Custom Serializer for Django Rest Framework - django

I'm searching for a solution to create a serializer / API Endpoint to represent data in a custom order. When adding serializers and viewsets to DRF, I only get the fields associated with that Model. But what I like to have is a custom structure of all my models together. As an example:
I have a model called season, a model called evenings and a model called events. Now I'd like to have an API Endpoint to have that all together, like so:
{
"requestTime": "2021-11-09 08:20",
"requestURL": "/all",
"requestMethod": "GET",
"responseCode": 200,
"season": "2021/2022",
"evenings": [
{
"evevning_id": 0,
"day": "",
"date": "2021-11-11",
"event_count": 2,
"events": [
{},
{}
]
}
]
}
For data structure in the models I have some ForeignKeys like:
season
|
evening
|
event
Any suggestions how to achieve this?

Use nested serializer (Season > Evening > Event) like this.
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
fields = ['id',...]
class EveningSerializer(serializers.ModelSerializer):
events = EventSerializer(many=True)
class Meta:
model = models.Evening
fields = ['id', 'day', 'date','events',...]
class SeasonSerializer(serializers.ModelSerializer):
evenings = EveningSerializer(many=True)
class Meta:
model = models.Season
fields = ['id', 'season', 'evenings',...]
make sure when fetching season from database, use prefetch related in queryset.

Related

How to make dynamic field serializer in Django RestFramework

I want to add Dynamic Field Array serializer in my drf project:
My get response looks something like this:
{
"title": "some",
"created_at": "2022-03-06T15:59:52.684469Z",
"fields": [
{
"id": 1,
"title": "Some title?",
"parent_field": 1
},
{
"id": 2,
"title": "Yet another fields",
"parent_field": 1
}
]
}
This is the item detail serializer, and fields is another model serializer. I achieved this by using this code:
class AnotherFieldSerializer(serializers.ModelSerializer):
class Meta:
model = AnotherModel
fields = "__all__"
class FirstSerializer(serializers.ModelSerializer):
fields = serializers.SerializerMethodField()
class Meta:
model = FirstModel
fields = "__all__"
def get_fields(self, obj):
serializer_context = {'request': self.context.get('request')}
children = obj.fields
if not children.exists():
return None
serializered_children = FirstSerializer(
children,
many=True,
context=serializer_context
)
return serializered_children.data
This works only for GET requests I want this to also work with POST and PUT requests. So, imagine I want to add/edit an item into my model FIRST model and add fields associated with it by just sending this JSON:
{
"title": "some",
"created_at": "2022-03-06T15:59:52.684469Z",
"fields": [
{
"id": 1,
"title": "Some title?",
},
{
"id": 2,
"title": "Yet another fields",
}
]
}
I know I can get fields from the response and loop through each item to create an instance of Another Model but fields validation will be much harder I think. But if there's more of a drf of doing this thing then it would be great. Also, I have no problem with making another serializer just for POST and PUT requests.
I hope my English was understandable.
You can create a new serializer for validation. To validate multiple items you can do the following,
class PostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=200)
created_at = serializers.DateTimeField()
fields = FirstSerializer(many=True) # your model serializer for the FirstModel
Ref : https://www.django-rest-framework.org/api-guide/serializers/#listserializer

Create a field with conditions

I'm a beginner in django rest framework.
I am wondring if there is a way to create a field from a GET. For example if "count" == 0, create a field named "available" : "out_of_stock" else "available
models.py
class Count(models.Model):
name = models.CharField(max_length=100)
count = models.IntergerField()
serializers.py
class CountSerializer(serializers.ModelSerializer):
class Meta:
model = Count
fields = '__all__'
views.py
class CountViewSet(viewsets.ModelViewSet):
queryset = Count.objects.all()
serializer_class = CountSerializer
output
[
{
"id": 1,
"count": 10,
},
{
"id": 2,
"count": 0,
}
]
First, for a good practise, avoid using fields = '__all__' and instead define your fields specifically.
For ex: fields = ['name', 'count'], you will see doing so will pay off shortly as I continue.
DRF has the feature to mark a serializer field as read_only or write_only so it works on specific requests, as it looks from the naming read_only intended for GET and write_only for POST.
You can do what you are looking for in so many ways, but I think the easiest way to do it would be using SerializerMethodField like this:
class CountSerializer(serializers.ModelSerializer):
available = serializers.SerializerMethodField()
def get_available((self, obj):
value = 'out_of_stock' if obj.count == 0 else 'available'
return value
class Meta:
model = Count
fields = ['name', 'count', 'available']
For more advanced needs, you can read about dynamic serializer fields on drf docs, see.

Django rest framework POST many to many with extra fields

I am trying to create a model in Django that has a many-to-many relationship to another model with extra fields. I am using the rest framework to provide CRUD operations on these and am having a chicken-and-egg scenario I believe...
The issue is that when I go to POST the new MainObject, it throws an error in the many-to-many part due to not having a MainObject id to point to. But I want it to point to the MainObject I am creating, which doesn't exist at time of POST'ing. I believe this to be an issue with the serializers, but am unsure of how to resolve it. I assume my assumptions might also be off in how I am formulating the POST data.
I am using Django 2.1.8
Model Code
class RelatedObject(models.Model):
...
class MainObject(models.Model):
related_objects = models.ManyToManyField(RelatedObject, through='ManyRelatedObject')
class ManyRelatedObject(models.Model):
main_object = models.ForeignKey(MainObject, on_delete=models.DO_NOTHING)
related_object = models.ForeignKey(RelatedObject, on_delete=models.DO_NOTHING)
other_attribute = models.BooleanField(...)
Serializer Code
class ManyRelatedObjectSerializer(serializers.ModelSerializer):
main_object = serializers.PrimaryKeyRelatedField(queryset=MainObject.objects.all())
related_object = serializers.PrimaryKeyRelatedField(queryset=RelatedObject.objects.all())
class Meta:
model = ManyRelatedObject
fields = '__all__'
class MainObjectSerializer(serializers.ModelSerializer):
related_object = ManyRelatedObjectSerializer(many=True)
class Meta:
model = MainObject
fields = '__all__'
POST Payload
( It is assumed that there exists a RelatedObject that has an id of 1)
{
"related_object": [
{
"related_object": 1,
"other_attribute": true
}
],
...
}
Response
{
"related_object": [
{
"main_object": [
"This field is required."
]
}
]
}
Goal Response:
{
"id": 1,
"related_object": [
{
"main_object": 1,
"related_object": 1,
"other_attribute": true
}
],
...
}
REST endpoint setup
class MainObjectViewSet(viewsets.ModelViewSet):
queryset = MainObject.objects.all()
serializer_class = MainObjectSerializer
Override the __init__() method of the MainObjectSerializer.
class MainObjectSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.context['request'].method == 'GET':
self.fields['related_object'] = ManyRelatedObjectSerializer(many=True)
related_object = ManyRelatedObjectSerializer(many=True)# remove this line
class Meta:
model = MainObject
fields = '__all__'
What this snippt do is, the serializer will render the response/output using ManyRelatedObjectSerializer serializer, if the request is a HTTP GET, otherwise it will render the stock mode(PrimaryKeyRelatedField)
For posterity, ended up hand-jamming this with poorly overridden create and update methods, due to time constraints. Seems ridiculous that django can't handle this scenario, seems like it's a fairly common use case...

Django Rest Framework JSONAPI and sideloaded/included resources

I am using the Django Rest Framwork JSON API for my Ember back end.
The (data) response I am getting back includes the "relationship" key but I need to sideload resources for a particular model, and hence want to include the "included" key as shown on the Ember docs https://guides.emberjs.com/release/models/relationships
I have a Product model with a FK relationship to a Tax model.
Here is my tax serializer:
from rest_framework_json_api import serializers
from .models import Tax
class TaxSerializer(serializers.ModelSerializer):
class Meta:
model = Tax
fields = ('id', 'name', 'amount')
Here is my product serializer:
from rest_framework_json_api import serializers
from .models import Product
from tax.serializers import TaxSerializer
included_serializers = {
'tax': TaxSerializer
}
class Meta:
model = Product
fields = ('id', 'name', 'image', 'price','tax')
class JSONAPIMeta:
included_resources = ['tax']
For this I've followed the example from https://www.mattlayman.com/blog/2017/sideload-json-api-django/
However, my response still includes the "relationships" key, and not the "included" key eg
"data" : [
{
"type":"products",
"id": "1",
"attributes": {...omitted for brevity ...
},
"relationships": {
"tax": {
"data": {
"type":"tax",
"id":"1"
}
}
}
},
{...etc....}
]
Update:
I am now getting the included key back in the response which is great. However, the whole point of doing this was that in my Ember models I don't have to create explicit relationships... from the Ember docs
when the API returns a deeply nested, read-only object or array, there
is no need to create multiple models with DS.attr('hasMany') or
DS.attr('belongsTo') relationships. This could result in a potentially
large amount of unnecessary code. You can access these objects in the
template without transforming them. This can be done with DS.attr()
(No attribute type).
I have this done in my Product model in Ember:
tax: DS.attr()
In my templates, assuming I already have a product instance I would expect to to be able to access product.tax.amount - but I can't.
user serializer depth= 1 or 2
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ('id', 'account_name', 'users', 'created')
depth = 1

Django serializer field not working

I did not find a solution although I looked at the questions asked...
When I use this serializer:
class MessageSerializer(ModelSerializer):
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = '__all__'
I get something like this:
{
"id": 62,
"sender": {
"pk": 12,
"email": "john#gmail.com",
"full_name": "John",
"profile_photo": null
},
"created_at": "2018-04-29T00:54:50.437662",
"message": "sdkjnasljdhkajsjdlasdasda",
"read_at": false,
"target": 18
}
I would like the target field to be like sender, that is: display the full user information instead of just the ID.
I tried to add this line: target = UserMobileSerializer(), but I still get only the ID in the output. I also tried target = UserMobileSerializer(read_only=True) but nothing changed.
You are not adding the field you defined to the fields in the serializer's Meta. Try this:
class MessageSerializer(ModelSerializer):
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = ('your', 'fields', 'sender')
EDIT: You need to serialize target, too. Like this:
class TargetSerializer(ModelSerializer):
class Meta:
model = Target
fields = ('id', 'title') # Add fields you want to get in the response.
class MessageSerializer(ModelSerializer):
target = TargetSerializer(read_only=True) # You should have TargetSerializer defined
sender = UserMobileSerializer(read_only=True)
class Meta:
model = Messages
fields = ('your', 'fields', 'target', 'sender')