How to create new model with foreignkey in django - python

class Schedule(models.Model):
name = models.CharField(max_length=50)
class ScheduleDefinition(models.Model):
schedule = models.ForeignKey(Schedule, on_delete=models.DO_NOTHING)
config = JSONField(default=dict, blank=True)
These are my models. I am trying to create a new ScheduleDefinition(The Schedule already exists and I know the ID I want to use for my foreign_key). I have a predefined Schedule id that I want to use, but it is not working..
Posting this body:
{
"schedule_id": 1,
"config": {
"CCC": "ccc"
}
}
Error I get:
null value in column "schedule_id" violates not-null constraint
What am I doing wrong? When I create new ScheduleDefinition models, the Schedule model will already be created previously. I am never going to be creating new Schedule's when I create new ScheduleDefinition's.
Serializer:
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'name']
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(read_only=True, many=False)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'config']
View:
from rest_framework import generics
from .models import Schedule, ScheduleDefinition
from .serializers import ScheduleSerializer, ScheduleDefinitionSerializer
class ScheduleList(generics.ListAPIView):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
class ScheduleDefinitionList(generics.ListCreateAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
class ScheduleDefinitionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = ScheduleDefinition.objects.all()
serializer_class = ScheduleDefinitionSerializer
View error:
File "/app/server/schedules/serializers.py", line 13, in ScheduleDefinitionSerializer
schedule_id = serializers.PrimaryKeyRelatedField(source="schedule")
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 247, in __init__
super().__init__(**kwargs)
File "/usr/local/lib/python3.7/dist-packages/rest_framework/relations.py", line 108, in __init__
'Relational field must provide a `queryset` argument, '
AssertionError: Relational field must provide a `queryset` argument, override `get_queryset`, or set read_only=`True`.

You've specified Schedule as required (not null), but you aren't actually posting any information to it. Currently your serializer is expecting information in the form:
{
"schedule": {
"name": "Foo"
},
"config": {...}
}
schedule_id is being discarded when you post. Furthermore, you've specified that schedule is a read only field, meaning even if you posted a schedule, it would still be discarded.
If you'd like to post to foreign keys, you'll either need to specially handle it by manually writing the create/update logic for a writable nested serializer (which can be a bit of a hassle), or use a different (writable) foreignkey field serializer and serialize your other read-only data another way.
For example, the following setup should work (untested) with the data you're currently trying to POST:
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = serializers.PrimaryKeyRelatedField(
queryset=Schedule.objects.all()
)
schedule_name = serializers.CharField(read_only=True, source="schedule.name")
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_name', 'config']
With this your post should work, and you'll still have read-only access to the corresponding schedule's name via the schedule_name field in your list/detail views.
EDIT
My earlier version of the code would not have been compatible with the original desired POST data. The following should work without altering the POST
class ScheduleDefinitionSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(many=False, read_only=True)
schedule_id = serializers.PrimaryKeyRelatedField(
source="schedule",
queryset=Schedule.objects.all()
)
class Meta:
model = ScheduleDefinition
fields = ['schedule', 'schedule_id', 'config']

Related

Joining Models in Python DRF Serializer without introducing Foreign key

So I am relatively new to Django, and DRF. I recently started a project to learn more about the platform.
I also found best practice guide line which I am following at the moment. Here is the Django Style Guide
The guild line says that every App should completely decouple itself and use UUID instead of foreign key,
And every business rule should go into services.py (Which I like btw.)
Here comes the problem. I am not sure how I can construct two models together to produce a nested JSON Output.
Ex: I have Post() model and Comments() model. and Comment model uses uuid of Post model as a referential id.
Now in my API.py, I am not sure how I can join these two together in the Serializer.py
FYI the code below is only for demo purposes, may not be executable
Model
class Post(models.Model):
user_id = models.UUIDField(default=uuid.uuid4)
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
pictures = models.CharField(max_length=100)
lat = models.CharField(max_length=16)
long = models.CharField(max_length=16)
vote = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
post_id = models.UUIDField(default=uuid.uuid4)
parent_id = models.ForeignKey("self", on_delete=models.CASCADE)
text = models.CharField(max_length=1000)
up_vote = models.IntegerField()
down_vote = models.IntegerField()
user_id = models.UUIDField(default=uuid.uuid4)
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now_add=True)
Service
def get_report(id) -> Dict:
logger.info('Get an Report by Id')
post= Post.objects.get(id=id)
return {
'post'= post
}
def get_comments(id) -> Dict:
logger.info('Get an Report by Id')
comments = Comment.objects.filter(post_id=id)
return {
'comments' = comments
}
API
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentsSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)
You actually don't need a services file to do what you're asking. DRF can handle what you want directly with serializers and using a RetrieveAPIView.
So using your models you have above you could have something like the following:
serializers.py
from rest_framework import serializers
from . import models
class CommentSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = models.Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = serializers.SerializerMethodField()
class Meta:
model = models.Post
fields = '__all__'
def get_comments(self, obj):
comments = models.Comment.objects.filter(post_id=obj.id)
return CommentSerializer(comments, many=True).data
views.py
from rest_framework import generics
from . import serializers, models
class ReportApi(generics.RetrieveAPIView):
serializer_class = serializers.PostSerializer
queryset = models.Post.objects.all()
You would need to specify the URL to pass in the primary key of the Post object you want to retrieve like so:
urls.py
from django.urls import path
from api import views
app_name = 'api'
urlpatterns = [
path('get_post_report/<pk>/', views.ReportApi.as_view()),
]
Then you would access the view using something like http://example.com/api/get_post_report/12345678/.
Note: You must configure the urls.py within your project's
urls file to use 'api/' for including your app's urls for the
'/api/' part of the url above to be a part of the url.
If you don't know how to set up urls refer to the Django Tutorial
This will then give you something like the following:
response.json
{
"id": 1,
"comments": [
{
"id": 1,
"post_id": "00000000-0000-0000-0000-000000000001",
"text": "Comment Text",
"up_vote": 0,
"down_vote": 0,
"user_id": "00000000-0000-0000-0000-000000000001",
"created_at": "2020-05-29T13:14:07.103072Z",
"modified_at": "2020-05-29T13:14:07.103124Z",
"parent_id": null
}
],
"user_id": "00000000-0000-0000-0000-000000000001",
"title": "My Post",
"description": "Post Description",
"pictures": "No Pictures",
"lat": "12",
"long": "12",
"vote": "12",
"created_at": "2020-05-29T13:14:07.102316Z",
"modified_at": "2020-05-29T13:14:07.102356Z"
}
Lastly
I looked at the guide you are referring to and did not see a reference to decouple models with UUID's. While you can do that (and in some cases may be necessary) I would think hard on whether you truly need that much decoupling.
There are reasons to use foreign keys over UUID's such as accessing the related models easier and faster. Decoupling using UUID's means you will need to write more boilerplate code every time you need to access a related model.
It comes down to deciding whether you need decoupling or a better developer experience with (subjectively) cleaner code. Please don't just follow a guide and assume it's law. More experience will help with this.
For example, if you used a foreign key relationship your PostSerializer could look like the following:
example_serializers.py
class PostSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(read_only=True)
comments = CommentSerializer(many=True)
class Meta:
model = models.Post
fields = '__all__'
See how we got rid of the get_comments(self, obj) method? That was just one bit of boilerplate that we got rid of by making the design decision to use foreign key relationships. Now just imagine a codebase of millions of lines of code and many serializers. Remember, the more code you write the more testing/debugging you need to do as well.
Again, just my opinion, but be sure you actually need to decouple your models before you do it.
Also, I strongly recommend you follow the DRF tutorial. It reviews everything you need to accomplish what I just posted here.
Hope this helps!
You could create one serializer called OutputSerializer and include the comments as its own field? if you change comments = OutputCommentSerializer to comments = CommentsSerializer(many=True, read_only=True) I believe that will depict what you want...(example below). You already defined CommentSerializer, thus you can use it.
class ReportGetApi(APIView):
class OutputSerializer(serializers.ModelSerializer):
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ('id', 'title', 'description', 'pictures', 'lat', 'long', 'vote', 'comments')
class CommentsSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = ('post_id', 'parent_id', 'text', 'up_vote', 'down_vote', 'user_id', 'created_at', 'modified_at')
def get(self, request):
post = PostService.get_post() #Only one item
comments = PostService.get_comments() #Many Items
serializer = self.OutputSerializer(post, many=True)
return Response(serializer.data)

What is the purpose of the class meta in Django?

For what purpose is the class Meta: used in the class inside the Django serializers.py file?
Serializer classes can also include reusable validators that are
applied to the complete set of field data. These validators are
included by declaring them on an inner Meta class. Also when
you are defining a serializer then meta tags will help the serializer
to bind that object in the specified format
Below are some of the examples :
While validating request data in specified format:
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
# Each room only has one event per day.
validators = UniqueTogetherValidator(
queryset=Event.objects.all(),
fields=['room_number', 'date']
)
While getting data from db
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
More you can read here
The Meta class can be used to define various things about the model such as the permissions, database name, singular and plural names

Property field in a django model is not serialized. How to fix?

I have the following model:
def get_file_path(instance, filename):
return 'files/{0}/{1}'.format(instance.user.username, filename)
class File(models.Model):
fid = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
file = models.FileField(upload_to=get_file_path)
when I try to serialize this model using for example serializers.serialize('json', File.objects.all()) I get:
u'[{"model": "app_name.file", "pk": "45f616b4-d028-488e-83c5-878ce1028018", "fields": {"user": 1, "file": "files/username/filename.txt"}}]'
I want the serialized json to also include the file field's url which file_obj.file.url returns so I added a property field like so:
class File(models.Model):
fid = models.UUIDField(primary_key=True, default=uuid.uuid4)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
file = models.FileField(upload_to=get_file_path)
def _get_file_url(self):
"Returns file's url"
return self.file.url
url = property(_get_file_url)
However property field url is still not serialized. So I tried to write a custom serializer for the model by extending serializers.ModelSerializer like so:
class FileSerializer(serializers.ModelSerializer):
class Meta:
model = File
fields = ('fid', 'user', 'file', 'url')
which doesn't work and I get 'module' object has no attribute 'ModelSerializer'. is it because django doesn't have ModelSerializer class and only django rest_framwork does ? if so how do I solve this if I am using django only and not django rest framework ?
Thanks.
when not using DRF - what is a poor idea...
you have to build manually in your view the dict with the data you wish to return and then use from django.http.response import JsonResponse for serializing the result.
def single_object_representation(obj):
return {
'fid': obj.fid,
'user': obj.user.username,
'url': obj._get_file_url()
}
def demo_view(request):
objects_to_serialize = File.objects.all()
result = [single_object_representation(obj) for obj in objects_to_serialize]
return JsonResponse(result, safe=False)
using django.core.serializers
see here: https://docs.djangoproject.com/en/2.0/topics/serialization/#subset-of-fields
but make sure it will work with your property. For some fields like User you should use duble underscore notation __ e.g user__username
otherwise (with DRF) - what I strongly recommend to you
First of all install drf:
pip install djangorestframework
Then if there is still problem with your serializer give a try to the SerializerMethodField (if in doubt see the docs: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
from rest_framework import serializers
class FileSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
class Meta:
model = File
fields = ('fid', 'user', 'file', 'url')
def get_url(self):
return self. _get_file_url()

Tastypie POST Does not FAIL

So I created a simple model as follows
class Titles(models.Model):
titleID = models.CharField(max_length=20,primary_key=True)
title = models.CharField(max_length=100)
class Meta:
verbose_name = "Titles"
verbose_name_plural = verbose_name
def __str__(self):
return self.title
Exposed it as a API as
class TitlesResource(AT.MultipartResource,AT.WrapView,ModelResource):
class Meta:
queryset = coreModels.Titles.objects.all()
authentication = AT.cxenseAMSAPIAuthentication()
authorization=Authorization()
resource_name = 'titles'
allowed_methods = ['get','post','put','patch']
include_resource_uri=False
limit=1000
When I try to create a new object it works but if I mess up any of the fields it still works
eg:
http://localhost:8000/core/titles/
{
"I_am_not_suppling_a_correct_feild": "2",
"title_not": "dept 1"
}
[27/Oct/2017 10:54:12] DEBUG [django.db.backends:90] (0.001) UPDATE "core_titles" SET "title" = '' WHERE "core_titles"."titleID" = ''; args=('', '')
Shouldnt this fail as I am not supplying the needed fields?
When I try to create a new object it works but if I mess up any of the fields it still works
post data can have N no. of fields. It depends how you are handling each one of them.
Shouldn't this fail as I am not supplying the needed fields?
No. When a POST request is made for a ModelResource it is routed to obj_update and then to obj_create unless it is overridden. There it takes value from **kwargs and creates a model entry. In your case it's not there thus taking empty strings.
For reference, look at the documentation:
https://django-tastypie.readthedocs.io/en/latest/non_orm_data_sources.html

Is there a way to get JSON data without tying to model in django restframework?

I would like to get JSON data on my backend without tying it to a model. For example, in json data like this I want access to quantity but don't want it tied to any model.
{
"email": "some#gmail.com"
"quantity": 5,
"profile": {
"telephone_number": "333333333"
}
}
My serializer:
class PaymentSerializer (serializers.ModelSerializer):
profile = UserProfilePaymentSerializer()
# subscription = UserSubscriptionSerializer()
class Meta:
model = User
fields = ('email', 'profile',)
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = AuthUser.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
def update (self, instance, validated_data):
instance.quantity = validated_data.get('quantity', instance.quantity)
print instance.quantity
return instance
When I send a PATCH request, I get an error
'User' object has no attribute 'quantity'
you can use json
import json
json.loads('{ "email": "some#gmail.com","quantity": 5,"profile": {"telephone_number": "333333333"}}')[u'quantity']
I think you can use simple json too (not tested):
from urllib import urlopen
import simplejson as json
url = urlopen('yourURL').read()
url = json.loads(url)
print url.get('quantity')
AFAIK, passing an extra data to ModelSerializer and attribute missing in model will throw as you stated. If you want to have extra attribute which is not present in model, restore_object needs to overriden.
Few examples GitHub Issue and similar SO Answer
You could try something like this:
class PaymentSerializer (serializers.ModelSerializer):
...
quantity = serializers.SerializerMethodField('get_quantity')
class Meta:
model = User
fields = ('email', 'profile', 'quantity',)
def get_quantity(self, user):
return len(user.email)
...
Basically, you modify Meta, but not the actual model, to add an extra quantity field to the output. You define this field as a SerializerMethodField. The serializer will invoke the method get_quantity to get the value of the field. In the example, the quantity is returned equal to the length of the email address of the user. This is useful in general for computed fields. These derived fields are automatically read-only and will be ignored on PUT and POST.
Reference is here.

Categories

Resources