Django Rest Framework Generic Relationships and ViewSets - django

I have a model Comment that can go on either Project or Task.
class Comment(BaseCommentModel):
author = models.ForeignKey(settings.AUTH_USER_MODEL)
content_type = models.ForeignKey(
ContentType,
verbose_name=_('content type'),
related_name="contenttype_set_for_%(class)s"
)
object_pk = models.TextField(_('object ID'))
content_object = GenericForeignKey(ct_field="content_type", fk_field="object_pk")
Project and Task have the field set up as:
comments = GenericRelation(Comment)
Comments can be created on either Projects or Tasks so there should be a viewset for each:
class ProjectCommentViewSet(viewsets.ViewSet):
class TaskCommentViewSet(viewsets.ViewSet):
And those would display the comments related to each model.
But what I don't understand is:
How do I set up the create/update/delete in a viewset for the Comment model so that the comment is created with the correct relationship?
How do I filter inside the viewsets to display comments related to that model? I can't use select_related because the Comment doesn't have a Project or Task field.
How do I write the HyperlinkedModelSerializers for these relationships? Do I need to add a HyperlinkedIdentityField to CommentSerializer() and then HyperlinkedRelatedFields to the User, Project, and Task Serializers? Or how do I set this up?
Thanks for any help provided, I could really use some direction here.
I'm having trouble understanding how the relationships on the models translate to the serializers and viewsets. And also how to handle the relationships when using generic relations.

The key is to use PrimaryKeyRelatedField. This will return an list of id's, which is what you would use for a create/update for a Project model instance with related Comment records.
Other than that, GenericRelation behaves just like other ORM relationships within django-rest-framework.
In the ViewSet Serializer define it like so:
from rest_framework import viewsets, serializers
class ProjectCommentViewSet(viewsets.ViewSet):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
class ProjectSerializer(serializers.ModelSerializer):
comments = serializers.PrimaryKeyRelatedField(
queryset=Comment.objects.all(), many=True, required=False)
class Meta:
model = Project
fields = ('id', 'etc...', 'comments',)

Related

DJANGO RF - Include the SerializerMethodField when nesting a serializer in another serializer

I have a serializer on my UserProfile model. It includes a SerializerMethodField "userGlobalPoints" :
class UserProfileSerializer(serializers.ModelSerializer):
userGlobalPoints = serializers.SerializerMethodField()
def get_userGlobalPoints(self, obj):
(*** calculation ***)
return userGlobalPoints
class Meta:
model = UserProfile
fields = '__all__'
In my Post serializer, I want to nest, for each post, information about the post's author. So I include fields from the UserProfile model (there is a ForeignKey relation in the Post model).
I would like to add the userGlobalPoints field. But I can't figure out how I can nest this field in the serializer. It looks like the usual syntax for nesting a serializer does not work on SerializerMethodField fields.
This is my PostSerializer:
class PostSerializer(serializers.ModelSerializer):
pk = serializers.ReadOnlyField()
authorNickname = serializers.CharField(source='postAuthor.userNickname', read_only=True)
authorProfileWithGlobalPoints = UserProfileSerializer(many=False, read_only=True)
class Meta:
model = Post
fields = ['pk','authorNickname','postText', 'postTimestamp', 'authorProfileWithGlobalPoints']
Any help on how to achieve this would be appreciated!
EDIT
Following a clever comment, the issue is not related to the SerializerMethodField. None of the fields of the nested serializer is serialized! I probably made a mistake in nesting the serializer in the reverse relation direction.
So the question does not need any more answers. Thank you all.
EDIT 2
The error was not caused by the MethodField. The issue is that I have nested serializers in the reverse direction of their relation. I tried to nest the author in the Post object, I should rather have nested the Post in the Author object. Since then I have changed my logic and it works.
Maybe an administrator could delete this topic, since it does not bring very much value to anybody.
What kind of error are you getting exactly?
Could it be that authorProfileWithGlobalPoints is not defined on the Post model? Maybe this solves it, by telling Django what to look for in the model:
class PostSerializer(serializers.ModelSerializer):
pk = serializers.ReadOnlyField()
authorProfileWithGlobalPoints = UserProfileSerializer(source='postAuthor', many=False, read_only=True)
class Meta:
model = Post
fields = ['pk','postText', 'postTimestamp', 'authorProfileWithGlobalPoints']

Conditionally nest Django serializers

So, I have a foreign key to my User model in many of my models. Now, the serializers for these models are nested, in that they include the entire user object rather than just the id. I have done so as shown bellow:
class BadgeSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Badge
fields = '__all__'
It works as expected. However, I seldom find myself in a situation where I want just the id. I was wondering what is the best way to conditionally nest my BadgeSerializer...
Now, the best solution I can think of is to have a non-nested BadgeSerializer, which includes only the user id. And then have a NestedBadgeSerializer (extending BadgeSerializer) which does nest the User model and include the entire user object.
class BadgeSerializer(serializers.ModelSerializer):
class Meta:
model = Badge
fields = '__all__'
class NestedBadgeSerializer(BadgeSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Badge
fields = '__all__'
I am NOT sure if that's the proper way though.

Django REST ModelSerializer --- General Question

I am working through a tutorial that includes the building of an articles app. I have an Article model that I am serializing and I am curious about why I need to explicitly set certain fields when using a ModelSerializer.
Here is my model:
from django.db import models
from core.models import TimestampedModel
class Article(TimestampedModel):
slug = models.SlugField(db_index=True, max_length=255, unique=True)
title = models.CharField(db_index=True, max_length=255)
description = models.TextField()
body = models.TextField()
author = models.ForeignKey('profiles.Profile', on_delete=models.CASCADE, related_name='articles')
def __str__(self):
return self.title
Pretty standard stuff. Next step is to serialize the model data in my serializers.py file:
class ArticleSerializer(serializers.ModelSerializer):
author = ProfileSerializer(read_only=True) # Three fields from the Profile app
description = serializers.CharField(required=False)
slug = serializers.SlugField(required=False)
class Meta:
model = Article
fields = (
'author',
'body',
'createdAt',
'description',
'slug',
'title',
'updatedAt',
)
Specifically, why do I need to explicitly state the author, description, and slug fields if I am using serializers.ModelSerializer and pulling those fields in from my model in my class Meta: below?
Thanks!
In the Django-Rest-Framework documentation, drf-docs/model_serializer/specifying-which-fields-to-include it says:
If you only want a subset of the default fields to be used in a model serializer, you can do so using fields or exclude options, just as you would with a ModelForm. It is strongly recommended that you explicitly set all fields that should be serialized using the fields attribute. This will make it less likely to result in unintentionally exposing data when your models change.
Therefore by using fields = in the Serializer META, you can specify just the needed fields, and not returning vital fields like id, or exessive information like updated and created timestamps.
You can also instead of using fields, use exclude, which again takes in a tuple, but just excludes the fields you don't want.
These are especially useful when your database table contains a lot of information, returning all this information, especially if it is listed, can result in large return JSON's, where the frontend may only use a small percentage of the sent data.
DRF has designed their framework like this to specifically combat these problems.
In my opinion, we should define field in serializer for:
Your api use serializer don't need all data of your models. Then you can limit field can get by serializer. It faster if you have so much data.
You dont want public all field of your model. Example like id
Custom field in serializer like serializers.SerializerMethodField() must define in fields for work
Finally, iF you dont want, you can define serializer without define fields. Its will work normally

Get relationship attributes with Django's REST API

There are two tables user, phone which are linked by an intermediate table owner. Here the goal is to use Rest API to get all phones from a specific user,
http://127.0.0.1/users/alice/phones/.
I use ModelSerializer as serializer and ViewSet as view. Please let me know how to get this done? I have no idea how to route /users/user_name/phones/ to get phones from a specific user.
Thanks.
Code snippet:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model=User
class PhoneSerializer(serializers.ModelSerializer):
class Meta:
model=Phone
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model=Owner
depth=1
// views
class UserViewSet(viewsets.ModelViewSet):
queryset=User.objects.all()
serializer_class=UserSerializer
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
....
I'd suggest you to create a filter.
It will be something like this:
1) Create filter (Make sure that django-filter is installed.):
# filters.py
import django_filters
class PhoneFilter(django_filters.FilterSet):
user = django_filters.Filter(name="user__user_name")
class Meta:
model = Phone
fields = ('user',)
2) Add filter to your ViewSet:
# views.py
class PhoneViewSet(viewsets.ModelViewSet):
queryset=Phone.objects.all()
serializer_class=PhoneSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_class = filters.PhoneFilter
And now you may use this url: /phones/?user=user_name.
Use #detail_route to route URL to your function, e.g.,
#detail_route(methods=['get'])
def phones(self, request, pk=None):
pass
http://127.0.0.1/users/alice/phones will work!

How can I relate two models (django tutorial's Poll and Choice) in a Tastypie API

I'm trying relate two resources (models) in an API using Tastypie but I'm getting an error.
I've followed the django tutorial and used:
models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
I tried to create a link between the Poll and Choice based on this stackoverflow answer and wrote the following code:
api.py
class ChoiceResource(ModelResource):
poll = fields.ToOneField('contact.api.PollResource', attribute='poll', related_name='choice')
class Meta:
queryset = Choice.objects.all()
resource_name = 'choice'
class PollResource(ModelResource):
choice = fields.ToOneField(ChoiceResource, 'choice', related_name='poll', full=True)
class Meta:
queryset = Poll.objects.all()
resource_name = 'poll'
When I go to: 127.0.0.1:8088/contact/api/v1/choice/?format=json
Everything works as it should. For example one of my choices links to the right poll:
{
"choice_text": "Nothing",
"id": 1,
"poll": "/contact/api/v1/poll/1/",
"resource_uri": "/contact/api/v1/choice/1/",
"votes": 6
}
When I go to: 127.0.0.1:8088/contact/api/v1/poll/?format=json
I get:
{
"error": "The model '<Poll: What's up?>' has an empty attribute 'choice' and doesn't allow a null value."
}
Do I need to use the fields.ToManyField instead or do I need to change my original model?
Tastypie recommends against creating reverse relationships (what you're trying to do here the relationship is Choice -> Poll and you want Poll -> Choice), but if you still wanted to, you can.
Excerpt from the Tastypie docs:
Unlike Django’s ORM, Tastypie does not automatically create reverse
relations. This is because there is substantial technical complexity
involved, as well as perhaps unintentionally exposing related data in
an incorrect way to the end user of the API.
However, it is still possible to create reverse relations. Instead of
handing the ToOneField or ToManyField a class, pass them a string that
represents the full path to the desired class. Implementing a reverse
relationship looks like so:
# myapp/api/resources.py
from tastypie import fields
from tastypie.resources import ModelResource
from myapp.models import Note, Comment
class NoteResource(ModelResource):
comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments')
class Meta:
queryset = Note.objects.all()
class CommentResource(ModelResource):
note = fields.ToOneField(NoteResource, 'notes')
class Meta:
queryset = Comment.objects.all()