serialize verbose fields of related foreign key object in django REST? - django

for an app with three objects - User, Event, and Action where users take actions on events creating an object like:
class Action(models.Model):
event = models.ForeignKey('CauseActivity', on_delete=models.CASCADE, related_name='actions')
user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='actions')
type = models.CharField(max_length=100, choices=ACTION_TYPES)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
I would like our RESTful API to be able to return the actions for one specific user, either within the user request (which requires no extra API calls) or with one additional request (get actions by user)
a simple User serializer with 'actions' as a field will return only the obj ids (response to GET ex: "actions":[108,109])
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
..., 'actions', )
I am aware of nesting to serialize custom fields like username = serializers.ReadOnlyField(source='user.name') but is there a way to use serializers.ListSerializer or .ModelSerializer to serialize the full set of Action objects' fields within my UserSerializer? I read through https://www.django-rest-framework.org/api-guide/serializers/ and best approach isn't really clear to me. thanks

create a serializer for your Action model (let's call it ActionSerializer) then make your code like
class UserSerializer(serializers.ModelSerializer):
actions = ActionSerializer(many=True)
class Meta:
model = User
fields = ('actions',...)

Related

display only some fields in get api response django serializer

I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples

Django subscriber feature

I am creating a django site/platform where the main concept is users can create shops and other users can subscribe to those who have shops open (think Etsy). Trying to implement the subscriber feature and this is a model I have so far for it:
class Subscriber(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
sub_shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
It works perfect for giving users the ability to subscribe and have the subscribtions listed in their profile and vice versa for shop owners, but for now a user can subscribe to a shop as many times as they want and I would like to prevent this. Idk if there is a constraint to allow multiple subscriber model instances by the same user but not allow for the same exact 'user' and 'sub_shop' instance OR if I am just going on about this in a very bad way!
You can use a UniqueConstraint [Django-doc] to specify that the combination of user and sub_shop should be unique:
class Subscriber(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
sub_shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['user', 'sub_shop'],
name='subscribe_once'
)
]
prior to django-2.2, you can work with unique_together [Django-doc]:
# prior to Django-2.2
class Subscriber(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
sub_shop = models.ForeignKey(Shop, on_delete=models.CASCADE)
class Meta:
unique_together = [['user', 'sub_shop']]
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.

Creating a Serializer to work with model relationships

I am new with Django Rest Framework and wanted to understand what's the accepted practice for writing Serializers that work with nested relationships.
Say, I have a models called Client and Invoice (this is just an illustrative example):
class Client(models.Model)
name = models.CharField(max_length=256)
class Invoice(models.Model)
client = models.ForeignKey(Client)
date = models.DateTimeField()
amount = models.DecimalField(max_digits=10, decimal_places=3)
I want to create a Serializer for Client that supports the following use cases:
Create a Client
When I create an Invoice, refer to the Client using its id.
Let's say I use this implementation:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = ClientSerializer()
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
def create(self, data):
client = Client.objects.get(pk=data['client']['id'])
invoice = Invoice(client=client,
date=datetime.fromisoformat(data['date']),
amount=Decimal(data['amount']))
invoice.save()
With this code, if I try to create an Invoice, I need the client object in the POST data to contain name as well. There is no config of the name field (read_only=True, write_only=True, required=False) that allows me to create and read Client as well as not be required when creating the Invoice.
How should this be solved?
Is the accepted practice that the request include the name field anyways?
Can we somehow created nested models like this? /api/Client/<id:client_id>/Invoice
Do we create multiple Serializer classes for each model - one for it's own viewset, and another for use in other models' viewsets?
Thanks!
This is an accepted pratice, but it has its advantages and disadvantages. Actual good practice depends on your actual needs. Here, as you suggested, while creating an Invoice, you also need to send a client name in the request, which should not be necessary. To overcome that need, one possible practive can be as follows:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
With this approach, you only include client's id in the serializer. You'll only need to send a client id in the requset with this approach, and don't need to write a custom create method on the serializer. Disadvantage of this approach is; you do not have the client name when listing invoices. so if you need to display client name when displaying an invoice, we'd need to improve this solution a bit:
class InvoiceSerializer(serializers.ModelSerializer):
client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
client_details = ClientSerializer(source='client', read_only=True)
class Meta:
model = Invoice
fields = ['id', 'client', 'client_details', 'date', 'amount']
With this approach, we have added a read-only field, client_details, that keeps the data in client serilaizer. So for write operations we use client field, which is only an id, and to read details about a client, we use client_details field.
Another approach could be defining a separate client serializer to be used as a child serializer in InvoiceSerializer only:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceClientSerializer(serializers.ModelSerializer):
name = serializers.CharField(read_only=True)
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = InvoiceClientSerializer()
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
def create(self, data):
client = Client.objects.get(pk=data['client']['id'])
invoice = Invoice(client=client,
date=datetime.fromisoformat(data['date']),
amount=Decimal(data['amount']))
invoice.save()
In this approach, we have defined a specia client serializer for use in InvoiceSerializer only, that has name field as read only. So while creating / updating an Invoice, you won't need to send client name, but while listing invoices, you get the client name. Advantage of this approach to the one before use, we do not need to use two separate fields for client field for writing and reading details.
For your second question, it is not supported out of the box by DRF, but you can take a look at this package, which provides that functionality and is listed on DRF's own documentation: https://github.com/alanjds/drf-nested-routers

How to write a Serializer for array data in REST API in Django Rest Framework?

I have written basic model serializers in Django where the api mimics the data model. I now have a requirement to store User Preference in database. The api contains an array.
My User Models :
class User(models.Model):
email_id = models.EmailField(max_length=80, blank=True, primary_key=True)
class UserPreference(models.Model)
email_id = models.ForeignKey('User')
preference = models.CharField(maxlength=20)
An ideal json post request would look something like this
{
email:"abhishek#gmail.com"
preference : [ 'books', 'food', 'lifestyle', 'travel']
}
I wish to save this json schema to the UserPreference model. This requires multiple inserts for preference. What will be a good serializer design for it ?
I tried
class UserPreferenceSerializer(serializers.ModelSerializer):
class Meta:
model = UserPreference
fields = ('email_id', 'preference')
you could use StringRelatedField of Django Rest Framework.
Make below changes and you will get the response in the way you want.
models.py (put related_name there)
class UserPreference(models.Model):
email_id = models.ForeignKey(User, related_name='preference')
preference = models.CharField(maxlength=20)
serializers.py
class UserSerializer(serializers.ModelSerializer):
preference = serializers.StringRelatedField(many=True)
class Meta:
model = User
fields = ('email_id', 'preference')
You could make your model like this:
class UserPreference(models.Model)
email_id = models.ForeignKey('User')
preference = models.ManyToManyField('Preference') #create model for preferences
Then add custom create method to your serializer:
def create(self, validated_data):
user = self.context.get('user') #you can pass context={'user': self.request.user} in your view to the serializer
up = UserPreference.objects.create(email_id=user)
up.save()
preference = validated_data.get('preference', [])
up.preference.add(*preference)
up.save()
return up

Update / patch / create nested models in Django Rest framework

I'm using Django 1.6.8, and Django Rest 2.4.4. I have a Person model with separate models for Address and PhoneNumbers.
class Person(models.Model):
address = models.OneToOneField(Address, blank=True, null=True)
phoneNumbers = models.ManyToManyField(PhoneNumber, blank=True)
class Address(models.Model):
address = models.CharField(max_length=50)
city = models.CharField(max_length=50)
state = models.CharField(max_length=2)
class PhoneNumber(models.Model):
number = models.CharField(max_length=15)
numberType = models.CharField(default='Mobile', max_length=15)
I'm using nested serializers in Django REST framework.
class PersonSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field('id')
address = AddressSerializer(source='address')
phoneNumbers = PhoneNumberSerializer(many=True)
This works for GET (I get address and phone numbers as nested json fields), but I need the same for PUT / POST / PATCH. Specifically, for each phone number, I want it to be updated if the id is specified, or created if there is no id in json. And the same for address, all in the same API call.
You need to implement your own create() and / or update() methods to support this.
It's explained in the Django Rest doc and it's available since Django Rest 3.0:
The following example demonstrates how you might handle creating a user with a nested profile object.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
(...)
Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default ModelSerializer .create() and .update() methods do not include support for writable nested representations.