Django-Rest nested serialization OneToOne - python

I've been looking and following the Django-Rest Official docs here and tried to base my approach on that, except I was using a OneToOne relationship.
However, when I go to add in the browsable interface, it doesn't pick up the value in the nested text fields, and tells me that it can't be null. I've tried Googling and searching on here, but can't really find anything that works for getting a OneToOne relationship to work like what I want. I'm new to the REST-framework, and am just really confused. Thanks!
Basically, each verb needs to have one past tense object, which has those three fields (for testing, more, or even another layer of nesting, will be added later). I just can't get them to add with the browsable API.
Models.py:
from django.db import models
class Verb(models.Model):
verb = models.TextField()
verbal_noun = models.TextField()
verbal_adjective = models.TextField()
present = models.TextField()
future = models.TextField()
habitual_present = models.TextField()
conditional = models.TextField()
past_habitual = models.TextField()
past_subjunctive = models.TextField()
present_subjunctive = models.TextField()
imperative = models.TextField()
class Past(models.Model):
verb = models.OneToOneField(Verb)
first_singular = models.TextField()
second_singular = models.TextField()
third_singular = models.TextField()
Serializers.py:
from rest_framework import serializers
from conjugations.models import Verb, Past
class PastSerializer(serializers.ModelSerializer):
class Meta:
model = Past
fields = ('first_singular','second_singular','third_singular')
class VerbSerializer(serializers.ModelSerializer):
past = PastSerializer()
class Meta:
model = Verb
fields = ('verb','verbal_noun','verbal_adjective','past','present',
'future','habitual_present','conditional','past_habitual',
'past_subjunctive','present_subjunctive','imperative' )
def create(self, validated_data):
past_data = validated_data.pop('past')
verb = Verb.objects.create(**validated_data)
for past in past_data:
Past.objects.create(verb=verb, **past)
return verb
Raw data input:
{
"verb": "test",
"verbal_noun": "test",
"verbal_adjective": "test",
"past": {
"first_singular": "test1",
"second_singular": "test2",
"third_singular": "test3"
},
"present": "test",
"future": "test",
"habitual_present": "test",
"conditional": "test",
"past_habitual": "test",
"past_subjunctive": "test",
"present_subjunctive": "test",
"imperative": "test"
}
Views.py
from conjugations.models import Verb
from conjugations.serializers import VerbSerializer
from rest_framework import generics, permissions
class VerbList(generics.ListCreateAPIView):
queryset = Verb.objects.all()
serializer_class = VerbSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class VerbDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Verb.objects.all()
serializer_class = VerbSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from conjugations import views
urlpatterns = [
url(r'^verbs/$', views.VerbList.as_view()),
url(r'^verbs/(?P<pk>[0-9]+)/$', views.VerbDetail.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
Error:
File "/home/Projects/Python/virtualenvs/remnigh/lib/python3.5/site-packages/rest_framework/mixins.py", line 26, in perform_create
serializer.save()
File "/home/Projects/Python/virtualenvs/remnigh/lib/python3.5/site-packages/rest_framework/serializers.py", line 191, in save
self.instance = self.create(validated_data)
File "/home/Projects/Python/reimnigh-test/reimnigh/conjugations/serializers.py", line 19, in create
past_data = validated_data.pop('past')
KeyError: 'past'

My serializers.py was messed up a little. I needed to remove the for loop for past_data since it was a one-to-one and just map that directly. Corrected file below:
from rest_framework import serializers
from conjugations.models import Verb, Past
class PastSerializer(serializers.ModelSerializer):
class Meta:
model = Past
fields = ('first_singular','second_singular','third_singular')
class VerbSerializer(serializers.ModelSerializer):
past = PastSerializer()
class Meta:
model = Verb
fields = ('verb','verbal_noun','verbal_adjective','past','present',
'future','habitual_present','conditional','past_habitual',
'past_subjunctive','present_subjunctive','imperative')
def create(self, validated_data):
past_data = validated_data.pop('past')
verb = Verb.objects.create(**validated_data)
Past.objects.create(verb=verb, **past_data)
return verb
It also helps to make sure your migrations are up to date too. That might have had a little bit to do with it.

Make sure your request is performed as json content type. HTML form don't support nested serializers.

Try changing your past field in the VerbSerializer to this:
past = PastSerializer(source='past_set')
This is syntax you could use if the Past-Verb relationship was a foreign key- I can't say for sure if it will work for you, but the DRF documentation says that for reverse relationships to be nested, a related name must be specified, either as above or in the model definition.
If this doesn't work, could you post the exact error/traceback?

Related

How to create and handle a related field in django with react?

I have created 2 models -
Tags and Startups. Startups has a tags field with a ManytoMany relationship with Tag.
Models.py file -
from django.db import models
from django_extensions.db.fields import AutoSlugField
from django.db.models import CharField, TextField, DateField, EmailField, ManyToManyField
class Tag(models.Model):
name = CharField(max_length=31, unique=True, default="tag-django")
slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])
def __str__(self):
return self.name
class Startup(models.Model):
name = CharField(max_length=31, db_index=True)
slug = AutoSlugField(max_length=31, unique=True, populate_from=["name"])
description = TextField()
date_founded = DateField(auto_now_add=True)
contact = EmailField()
tags = ManyToManyField(Tag, related_name="tags")
class Meta:
get_latest_by = ["date_founded"]
def __str__(self):
return self.name
My serializers.py file -
from rest_framework.serializers import HyperlinkedModelSerializer, PrimaryKeyRelatedField, ModelSerializer
from .models import Startup, Tag
class TagSerializer(HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = "__all__"
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "tag-api-detail"
}
}
class StartupSerializer(HyperlinkedModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Startup
fields = "__all__"
extra_kwargs = {
"url": {
"lookup_field": "slug",
"view_name": "startup-api-detail"
}
}
My viewsets.py file -
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from .serializers import TagSerializer, StartupSerializer
from .models import Tag, Startup
from rest_framework.decorators import action
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK, HTTP_204_NO_CONTENT
from django.shortcuts import get_object_or_404
class TagViewSet(ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
lookup_field = "slug"
class StartupViewSet(ModelViewSet):
serializer_class = StartupSerializer
queryset = Startup.objects.all()
lookup_field = "slug"
#action(detail=True, methods=["HEAD", "GET", "POST"], url_path="tags")
def tags(self, request, slug=None):
startup = self.get_object()
print(startup)
if request.method in ("HEAD", "GET"):
s_tag = TagSerializer(
startup.tags,
many=True,
context={"request": request}
)
return Response(s_tag.data)
tag_slug = request.data.get("slug")
if not tag_slug:
return Response(
"Slug of Tag must be specified",
status=HTTP_400_BAD_REQUEST
)
tag = get_object_or_404(Tag, slug__iexact=tag_slug)
startup.tags.add(tag)
return Response(HTTP_204_NO_CONTENT)
I can create startups and relate tags through my django admin.
I have a dropdown list with all the created tags in my django admin from where I can relate tags with startups.
I do not understand how can I relate tags with startup when creating startup in react.
I tried posting data in this form -
{
"name": "Test Startup 1",
"description": "First Desc",
"contact": "first#gmail.com",
"tags": [
{
"url": "http://127.0.0.1:8000/api/v1/tag/first-tag/",
"name": "First Tag",
"slug": "first-tag"
}
]
}
I cannot get the tags to relate with startup.
How to handle related fields?
You have multiple options.
If you want to perform association (meaning tags and startup already exists before making the request, kinda what happen with django-admin), you could create a new serializer that have a different field for tags, accepting ids instead of the nested serializer.
If you want to have nested creation/edition, you could checkout WritableNestedSerializer from here. Because the doc says that it does not handle such usecase, because they might be many way to perform this depending on your business logic, but provide ways to perform that yourself here
Another approach would be to have a route with nested ressources (with nested routers for instance) so when you POST a tag in /startup/1/tags/ you create AND associate your tag automatically, like you did.
Now, concerning your endpoint, you need to get the data of your request and pass it to the tag serializer. This serializer will then validate your data, and if it is valid, you can perform tag creation.
To do that, you can do something like:
tag_data = request.data
tag_serializer = TagSerializer(data=request.data)
tag_serializer.is_valid()
tag = tag_serializer.save()
tag.startup_set.add(startup)
Adding the relationship have to be done in two step. You should use a transaction to ensure it is correctly created.
Also, instead of adding this logic in your view, you should override your TagSerializer/StartupSerializer create method to do that.

How to create new model with foreignkey in django

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']

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)

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()

How to find JSON data as ember.js wants?

I am new in django and ember.js. Can you help me about how to find correct JSOn for ember.js
My Code is Here -
In models.py -
from django.db import models
class Confusion(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=100)
In serializers.py -
from rest_framework import serializers
from confusion.models import Confusion
class ConfusionSerializer(serializers.ModelSerializer):
class Meta:
model = Confusion
fields = ('id', 'title', 'description')
In views.py -
from rest_framework import generics
from confusion.models import Confusion
from confusion.serializers import ConfusionSerializer
class ConfusionList(generics.ListCreateAPIView):
queryset = Confusion.objects.all()
serializer_class = ConfusionSerializer
class ConfusionDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Confusion.objects.all()
serializer_class = ConfusionSerializer
Now I am getting Output Like -
[{"id": 1, "title": "Career", "description": "I am confused about my career"}]
But I need:
{"confusion":{"id": 1, "title": "Career", "description": "I am confused about my career"}}
Check out Toran Billups' ember-data-django-rest-adapter. It should do exactly what you need.
Take a look at the README — for basic use, all the extra set-up is on the JavaScript side.
(I basically quote...)
Include the ember-data-django-rest-adapter.js after ember-data.js in your HTML/build system
Add the custom adapter:
App.Store = DS.DjangoRESTStore.extend({
adapter: DS.DjangoRESTAdapter.create()
});
That's it. (In particular is there something you can't get working?)

Categories

Resources