Django REST framework reverse relationship object instance - django

Let's say that we have models like below
class Movie(models.Model):
"""Stores a single movie entry."""
title = models.CharField(max_length=200, blank=False)
class Watchlist(models.Model):
"""Stores a user watchlist."""
user = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name='watchlist',
on_delete=models.CASCADE)
movie = models.ForeignKey(Movie, related_name='watchlist',
on_delete=models.CASCADE)
added = models.BooleanField(default=False)
Serializer
class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for a custom user model with related user action."""
url = serializers.HyperlinkedIdentityField(
view_name='customuser-detail', lookup_field='username')
watchlist = serializers.HyperlinkedRelatedField(
many=True, view_name='watchlist-detail', read_only=True)
class Meta:
model = CustomUser
fields = ('url', 'username', 'watchlist')
and the view:
class CustomUserViewSet(viewsets.ReadOnlyModelViewSet):
"""
list:
Return a list of all the existing users.
retrieve:
Return the given user with user's watchlist.
"""
queryset = CustomUser.objects.all()
permissions = (IsAdminOrReadOnly)
lookup_field = 'username'
serializer_class = CustomUserSerializer
That all will give us a user and hyperlinked filed to the particular watchlist.
{
"url": "http://127.0.0.1:8000/api/v1/users/John/",
"username": "John",
"favorites": [
"http://127.0.0.1:8000/api/v1/watchlist/2/",
"http://127.0.0.1:8000/api/v1/watchlist/1/"
]
},
but instead of that I would like to get a particular movie instance like that.
{
"url": "http://127.0.0.1:8000/api/v1/users/John/",
"username": "John",
"favorites": [
"http://127.0.0.1:8000/api/v1/movies/33/",
"http://127.0.0.1:8000/api/v1/movies/12/"
]
},
so my question is how can I achieve that? I tried with hyperlinkedrelatedfield but nothing seems to work as expected.

You could use the SerializerMethodField along with reverse.
from rest_framework.reverse import reverse
class CustomUserSerializer(serializers.HyperlinkedModelSerializer):
"""Serializer for a custom user model with related user action."""
url = serializers.HyperlinkedIdentityField(
view_name='customuser-detail', lookup_field='username')
favorites = serializers.SerializerMethodField()
def get_favorites(self, obj):
movie_urls = [
reverse("movie-view", args=[watchlist.movie.id], request=self.context['request'])
for watchlist in obj.watchlist.all()
]
return movie_urls
class Meta:
model = CustomUser
fields = ('url', 'username', 'favorites')

Related

Allow related field in post request in DRF

I have created model with many to many relationship and I have join table when I keep additional variable for it:
class BorderStatus(models.Model):
STATUS_CHOICES = [("OP", "OPEN"), ("SEMI", "CAUTION"), ("CLOSED", "CLOSED")]
origin_country = models.ForeignKey(OriginCountry, on_delete=models.CASCADE, default="0")
destination = models.ForeignKey(Country, on_delete=models.CASCADE, default="0")
status = models.CharField(max_length=6, choices=STATUS_CHOICES, default="CLOSED")
extra = 1
class Meta:
unique_together = [("destination", "origin_country")]
verbose_name_plural = "Border Statuses"
def __str__(self):
return (
f"{self.origin_country.origin_country.name} -> {self.destination.name}"
f" ({self.status})"
)
Other models:
# Create your models here.
class Country(models.Model):
name = models.CharField(max_length=100, unique=True, verbose_name='Country')
class Meta:
verbose_name_plural = "Countries"
def __str__(self):
return self.name
class OriginCountry(models.Model):
origin_country = models.ForeignKey(
Country, related_name="origins", on_delete=models.CASCADE
)
destinations = models.ManyToManyField(
Country, related_name="destinations", through="BorderStatus"
)
class Meta:
verbose_name_plural = "Origin Countries"
def __str__(self):
return self.origin_country.name
Here is my serializer for the endpoint:
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""Create serializer for editing single connection based on origin and destination name- to change status"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
And my endpoint:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
The problem Im having is that I cant create any new combination for the BorderStatus model in this serializer via post request.
If I remove the lines:
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
Then the form will work, but then I wont have the string representation of those variables, instead I get IDs.
Is there any way to allow request to accept origin_country and destination while being related fields?
EDIT:
To clarify how OriginCountry works, it is has a nested field:
[{ "id": 1
"origin_country": "Canada",
"dest_country": [
{
"id": 1,
"name": "France",
"status": "CLOSED"
},
{
"id": 2,
"name": "Canada",
"status": "OP"
}
]
},
]
You can try to override perform_create method of the viewset to make the necessary adjustments on-the-fly when new entry is posted:
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
serializer_class = BorderStatusEditorSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
def perform_create(self, serializer):
origin_country, _ = models.Country.get_or_create(name=self.request.data.get('origin_country')
destination, _ = models.Country.get_or_create(name=self.request.data.get('destination')
return serializer.save(origin_country=origin_country, destination=destination)
Maybe you will also need to adjust your serializer to have:
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ['name']
class BorderStatusEditorSerializer(serializers.ModelSerializer):
origin_country = CountrySerializer()
destination = CountrySerializer()
...
Yes, I will try to give this combination.
You get this error because of Incorrect Type exception. Django checks data type validation on the serializer. For example here your dest_country returns a list of dicts but in your model it is a primary key (pk)
That's why on post django says : pk value expected, list received
But you can solve this error by using two different serializers (one to post another by default)
1. Create two different serializers
class BorderStatusEditorSerializer(serializers.ModelSerializer):
"""The First serialiser by default"""
origin_country = serializers.StringRelatedField(read_only=True)
destination = serializers.StringRelatedField(read_only=True)
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
class BorderStatusEditorCreateSerializer(serializers.ModelSerializer):
"""The Second serialiser for create"""
class Meta:
model = BorderStatus
fields = ('origin_country', 'destination', 'status')
2.Add get_serializer_class method to for Viewset
class BorderStatusViewSet(viewsets.ModelViewSet):
queryset = BorderStatus.objects.all()
filter_backends = (DjangoFilterBackend,)
filter_fields=('origin_country','destination')
serializer_classes = {
'create': BorderStatusEditorCreateSerializer, # serializer used on post
}
default_serializer_class = BorderStatusEditorSerializer # Your default serializer
def get_serializer_class(self):
return self.serializer_classes.get(self.action, self.default_serializer_class)

Initializing drf Serializer

I have two models: User and Employee. I want to return following json via drf:
[ { 'admin': {}, 'employee': [{}, {}] }, { 'admin': {}, 'employee': [{}, {}] } ].
However, I don't understand how to initialize a TeamSerializer with data. What should I pass into TeamSerializer
class Meta:
model = User
fields = [
"first_name",
"last_name",
"email",
"hourly_rate",
"role",
]
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model = Employee
fields = [
"first_name",
"last_name",
"email",
"hourly_rate",
"role",
]
class TeamSerializer(serializers.Serializer):
admin = AdminSerializer()
employee = EmployeeSerializer()
You will need to create a model for team to specify the relation.
If I understand right, each team has an admin and multiple employees, each employee can be member of one team, and an admin can administer one team. so one to one relation for admin and one to many relation for employee.
The model should look like this:
class Team(models.Model):
admin = models.OneToOneField(get_user_model(), on_delete=models.SET_NULL, null=True)
class Employee(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="employees")
# all the rest of your fields here
Then create a serializer for Team:
class TeamSerializer(serializers.ModelSerializer):
admin = UserSerializer()
employees = EmployeeSerializer()
class Meta:
model = Team
fields = ["admin", "employees"]
I would take that even farder and create a Admin model and AdminSerializer also to provide more information about the admin, but it depends on your needs.

foreign key serialization drf

i created a model named 'Post'.
here is the code:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(default=datetime.now, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
ordering = ['-date']
i want to get all objects of Post model with users firstname and lastname.
in views.py:
#api_view(['GET'])
#permission_classes((IsAuthenticated,))
def allPost(request):
allpost = Post.objects.all()
serializer = PostSerializers(allpost, many=True)
return Response(serializer.data)
in serialisers.py:
class UserSerializers(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class PostSerializers(serializers.ModelSerializer):
user = serializers.RelatedField(many=True)
class Meta:
model = Post
fields = ('body','date','user')
You can make a serialzier with firstname and lastname:
class SimpleUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('first_name', 'last_name')
and then use that serializer as subserialiser:
class PostSerializer(serializers.ModelSerializer):
user = SimpleUserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
This generates a JSON blob like:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"user": {
"first_name": "MyFirst",
"last_name": "MyLast"
}
}
or you use make use of two CharFields:
class PostSerializer(serializers.ModelSerializer):
first_name = serializers.CharField(source='user.first_name', read_only=True)
last_name = serializers.CharField(source='user.last_name', read_only=True)
class Meta:
model = Post
fields = ('body','date')
this generates as JSON blob:
{
"body": "Sample body text",
"date": "2020-12-11T12:34:56.789Z",
"first_name": "MyFirst",
"last_name": "MyLast"
}
Note: The name of a serializer class is normally singular, so PostSerializer instead of
PostSerializers.
There is no need to use related field in this case. many=True should be used if you're passing multiple objects (like you did with posts).
What you should do is user your UserSerializer in PostSerializer
class PostSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Post
fields = ('body','date','user')
Read docs for more info
https://www.django-rest-framework.org/api-guide/relations/#nested-relationships

DRF ListAPIView return manytomany value names instead of pk

I have a Post Model contains tags field that have ManyToManyField to categories Model,
when i call REST ListAPIView all post tags returns in pk
I have tried to override list function in ListAPIView and map all tags_names for every post
but this takes a huge time and destroys the performance
I hope/Believe there something built in for this case
models.py
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=256)
content = RichTextField()
tags = models.ManyToManyField(Categories)
def __str__(self):
return self.title
class Categories(models.Model):
tag_name = models.CharField(max_length=256, unique=True)
def __str__(self):
return self.tag_name
class Meta:
ordering = ('tag_name',)
unique_together = ('tag_name',)
views.py
from .models import Post
from .serializers import NewPostSerializer, PostSerializer
class NewPost(CreateAPIView):
serializer_class = NewPostSerializer
permission_classes = [IsAuthenticated, IsAdminUser]
class PostList(ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
serializers.py
class NewPostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['tags', 'author_id']
when i visit ListApiView link returned results would be like this:
[
{
"id": 30,
"title": "post title test",
"content": "lorem text",
"author": 3,
"tags": [
8, # should be games
3 # should be action
]
}
]
You can simply use SlugRelatedField, This will return a list of names instead of list of pks
from rest_framework import serializers
class NewPostSerializer(ModelSerializer):
tags = serializers.SlugRelatedField(
many=True,
read_only=True,
slug_field='tag_name'
)
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['author_id']
To optimize performance you should use prefetch_related. This reduces the number of queries to your database to only 1 request to fetch all the related tags for all of your posts.
class PostList(ListAPIView):
queryset = Post.objects.prefetch_related('tags').all()
serializer_class = NewPostSerializer
Now for the serialization of your tags you have to create a new serializer.
class TagSerializer(ModelSerializer):
class Meta:
model = Categories
fields = ['name']
You can then use this serializer in your NewPostSerializer.
class NewPostSerializer(ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ['title', 'content', 'tags']
read_only_fields = ['author_id']
The results of this should be "tags": [{"name": "ABC"},{"name": "EFG"}].

Django Rest Framework related fields

I'm new to Django I want to get profile image from another model topic
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User)
file = models.ImageField(upload_to='profile_image', blank=True)
def __unicode__(self):
return u'%s' % self.user
class Topics(models.Model):
user = models.ForeignKey(User)
title = models.charField(max_length = 55)
serializers.py
User = get_user_model()
class pic(serializers.ModelSerializer):
class Meta:
model = UserProfile
fields = ['file']
class UserInfo(serializers.ModelSerializer):
username = pic(read_only=True)
class Meta:
model = User
fields = ['username','first_name',]
class TopicSerializer(serializers.ModelSerializer):
user= UserInfo(read_only=True)
class Meta:
model=Topics
fields = ('user','title',)
I'm getting like this:
"user": {
"username": {},
"first_name": ""
},
"title": "Django the title",
Now I need every file field related to Topics field with the first name and email of the user.
I want to like this:
"user": {
"username": "akash",
"first_name": "Akash DK"
"file":"static/imag.png"
},
"title": "Django the title",
Thanks in advance
You could use a SerializerMethodField in your UserInfo serializer to lookup the profile image and add it to the output data.
class UserInfo(serializers.ModelSerializer):
file = serializers.SerializerMethodField()
def get_file(self, user):
return UserProfile.objects.get(user=user).file.url
class Meta:
model = User
fields = ['username','first_name', 'file']