How to use DRF serializers to use multiple serializers and also flatten the data sent - django-rest-framework

I am struggling to figure out the best way to write these serializers.
I have a model like this
class Foo(models.Model):
db = models.CharField(max_length=30)
name = models.CharField(max_length=30)
schema = models.CharField(max_length=50)
table_name = models.CharField(max_length=50)
ext_type = models.CharField(max_length=100)
sample_by = models.CharField()
sample_by_val = models.CharField()
Next I have my serializer with something like this
class SaveSerializer(serializers.ModelSerializer): #
"""
Cannot figure out the best way to do this.
This is just an example serializer I had in mind.
I want to understand a cleaner/best way to do this.
"""
sample = SampleSerializer()
exts = ExtSerializer()
class Meta:
model = Foo
Here is the problem - The data to this view is sent in this format
{
"sample": {"sample_by":"weight", "sample_by_val":[30,50,60],"name":"test"}
"exts": {"ext_type": "BY WEIGHT", "table":"EMPLOYEE"},
"db" : "foobar",
"schema" : "abcd"
}
class ExtSerializer(serializers.Serializer):
table = serializers.CharField()
ext_type = serializers.CharField()
class SampleSerializer(serializers.Serializer):
sample_by = serializers.CharField()
# Similar ones for sample_by_val and name
I am struggling to write SaveSerializer. It needs to first check the data that is being sent in a particular format and pass it through some serializers. Then it needs to flatten out that data format to store it in the model itself.
Can someone help me understand the best way to do this? It will be very helpful! thanks

You can override the to_representation() and to_internal_value(), I think it's best way.
Here is an example
Sorry, I'll eat my word, just override create method like this:
#override create method of serializer, you can easily handle object creation.
def create(self, validated_data):
#first, pop sample and exts from validated data.
sample = validated_data.pop('sample')
exts = validated_data.pop('exts')
#then, create object
foo = Foo.objects.create(**validated_data, **sample, **exts) #flat validated_data, sample, and exts to create Foo.
return foo
#maybe you also want to handle representation, since the model of Foo doesn't has ext and sample fields
def to_representation(self, instance):
sample = SampleSerializer(instance).data #SampleSerializer will deserialize sample_by field of Foo instance.
exts = ExtSerializer(instance).data
instance.sample = sample
instance.exts = exts
return super(SaveSerializer, self).to_representation(instance)

Related

DRF Serialize 2 fields into array

DRF v3.12.4
class AModel(models.Model):
field_0 = models.CharField()
field_1 = models.FloatField()
field_2 = models.FloatField()
Have such a model, and I need to serialize it (read-only) in such way:
{ "field_0": "FIELD0", "arr": [ 1.0, 2.0 ]}
For now, I make the next solution:
class BSerializer(serializer.Serializer):
def to_representation(self, data):
return [data.field_1, data.field_2]
class ASerilizer(serializers.Serializer):
arr = BSerializer(source='*')
class Meta:
model = AModel
fields = ['field_0', 'arr']
It's work, but I also use drf-spectacular and of course, it can't analyze the overriding method. I'm trying #extend_schema_serializer on BSerilizer, but it does not work.
Is it possible without overriding the to_representation method?
So, the question is it possible for such a case to use a serializer without overriding the to_representation method, ListSerializer for example? Or maybe someone knows why #extend_schema_serializer does not work?
Thx
If you want just use arr field as a read_only field, you can use serializer's SerializerMethodField:-
from rest_framework import serializers
class ASerilizer(serializers.Serializer):
arr = serializers.SerializerMethodField()
class Meta:
model = AModel
fields = ['field_0', 'arr']
def get_arr(self, instance):
return [instance.field_1, instance.field_2]
For more information you can refer from here https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

Django: customizing the field types needed for create and retrieve serializers

I currently have the following serializer:
serializers.py
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = SurfInstructorSerializer(many=False)
surfers = SurferSerializer(many=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
def create(self, validated_data):
return SurfGroup(**validated_data)
And the following viewset create method (viewset inherited from viewsets.ViewSet as we need some bespoke customization, extra signals and actions etc):
viewsets.py
# Surf Group Create View:
def create(self, request, format=None):
serializer = SurfGroupSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response = responses.standardized_json_response(
message='Surf Group Objects Have Been Successfully Created',
data=serializer.data
)
return Response(data=response, status=status.HTTP_201_CREATED, headers=headers)
For the retrieve action, the serializer works well, and we have a nested instructor object in the response. However, I want to perform a create by passing in the instructor uuid attrbiute like (see content in the POST textarea):
Rather than a whole object...I was wondering how we achieve this? Is it best to just have two Serializers, one for performing the create, and one the retrieval?
def create(self, validated_data):
surf_group = SurfGroup(
instructor__uuid=validated_data['instructor'],
)
surf_group.save()
return surf_group
It is good question.
I work with this situations many times and it looks like one option is to have two serializers as you mean: 1 for list/retrieve and 1 for save.
Another option (for me) is to set serializer field input as UUID and output as another serializer data like this:
class SurfGroupSerializer(serializers.ModelSerializer):
instructor = serializers.UUIDField()
surfers = SurferSerializer(many=True, read_only=True)
class Meta:
model = SurfGroup
fields = ['uuid', 'instructor', 'date', 'starting_time', 'ending_time', 'surfers']
# I use this validate method to transform uuid to object which will
# be bypassed to create method for easly save
def validate_instructor(self, instructor_uuid):
try:
return Instructor.objects.get(uuid=instructor_uuid)
except Instructor.DoesNotExist:
# Remember that you dont need to pass field_key: [errors] to ValidationError
# because validate_FIELD will automatically pass field_key for you !
raise ValidationError(['Instructor with the given uuid does not exist.'])
# Overwrite output data
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['instructor'] = SurfInstructorSerializer(instance=instance.instructor).data
return ret

How to post data to Embedded document with Mongoengine REST

I am trying to use Django with mongoengine to make an API.
So far I can get the objects and delete them. but when I want to post some data. Lets say student + course it is giving an error:
type object 'Course' has no attribute 'objects'
Models en ..
#Model.py
class Course(EmbeddedDocument):
course_name = StringField(max_length=200)
course_fee = StringField(max_length=200)
class Student(Document):
student_name = StringField(max_length=200)
student_contactperson = StringField(max_length=200)
student_adress = StringField(max_length=200)
courses = ListField(EmbeddedDocumentField(Course))
#Serializers.py
class CourseSerializer(EmbeddedDocumentSerializer):
class Meta:
model = Course
fields = ('course_name','course_fee')
class StudentSerializer(DocumentSerializer):
courses = CourseSerializer(many=True)
class Meta:
model = Student
fields = ('student_name','student_contactperson','student_adress','courses')
depth = 2
def create(self, validated_data):
course_data = validated_data.pop('courses')
student = Student.objects.create(**validated_data)
Course.objects.create(student=student, **course_data)
return student
#Views.py
class StudentViewSet(meviewsets.ModelViewSet):
lookup_field = 'name'
queryset = Student.objects.all().order_by('-date_joined')
serializer_class = StudentSerializer
A Document represents a MongoDB document (i.e a record in a collection), a Document class is bound to a particular collection. An EmbeddedDocument represents a structure that gets nested in a Document.
So by design an EmbeddedDocument isn't attached to any collection unless you embed it inside a Document.
This means that you can't query or save an EmbeddedDocument class, you need to query/save the parent Document.
Document.objects is an entry point for querying a collection, it only exists on Document classes. You are calling Course.objects.create but Course is an EmbeddedDocument.
I believe you need to change your code to the following
class StudentSerializer(DocumentSerializer):
...
def create(self, validated_data):
course_data = validated_data.pop('courses')
course = Course(**course_data) # assuming course_data is {course_name: ..., course_fee: ...}
return Student.objects.create(courses=[course], **validated_data)

How can i add #property field to django rest serializer with '__all__'

I have model
class A(models.Model):
is_enable = models.BooleanField(default=False)
title = models.CharField(max_length=255, blank=True, null=True)
updated_date = models.DateTimeField(auto_now=True)
show_count = models.IntegerField(default=0)
answers_count = models.IntegerField(default=0)
audience = JSONField()
events = JSONField()
rules = JSONField()
message = models.TextField(blank=True)
#property
def conversion(self):
if self.show_count == 0:
return 0.0
return (self.answers_count / self.show_count) * 100
And i have serializer
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
class Meta:
model = Trigger
fields = '__all__'
I want to add to response #property conversion` field
and I want to do something like this in serializer
class Meta:
model = Trigger
fields = '__all__' + conversion
I know that i can make something like this
class Meta:
model = Trigger
fields = ('is_enable', 'title' ... 'conversion')
But i want add all fields and conversion field and do this more beautiful
you can use somethig like this:
class ModelMixin:
#classmethod
def _get_model_fields(cls):
all_fields = cls._meta.get_fields()
fields = [i.name for i in all_fields if i.__class__.__name__ not in ['ManyToManyRel', 'GenericRelation', 'ManyToOneRel']]
return fields
#classmethod
def get_serializer_fields(cls):
fields = cls._get_model_fields()
return fields
class A(ModelMixin, models.Model):
...
class ASerializer(ModelSerializer):
custom_field = serializers.JSONField()
class Meta:
model = A
fields = A.get_serializer_fields()
fields.append('cusom_field')
Might still not be as clean as you want it to be, but using <model>._meta.fields you can get all fields of that model, and using the name property of each field you can retrieve the names that refer to them.
As the serializers file is interpreted as python, it supports code execution, meaning you can use all python functionalities when setting properties, including a for loop, which gives us the ability to do this:
fields = (tuple((f.name for f in A._meta.fields)) + ('conversion',))
Needed this for my own model as well and there it seemed to work, so hopefully so does it for you.
You can create a SerializerMethodField in your serializer and later define a method in serializer itself that can call your functions you defined in models using #property
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
conversion = serializers.SerializerMethodField()
class Meta:
model = Trigger
fields = '__all__'
def get_conversion(self,obj):
return obj.conversion
This should allow you to keep your code beautiful without modifying much of your code.

Django REST Framework - optimizing nested serialization of a queryset

I have a model and serializer like this:
models.py
class CalendarEvent(BaseMixin):
title = models.TextField(blank=True, null=True)
class CalendarEventReminder(BaseMixin):
event = models.ForeignKey(CalendarEvent, related_name = 'reminders')
minutes = models.CharField()
class Meta:
managed = False
db_table = 'calendar_event_reminder'
def __str__(self):
return self.minutes
serializer.py
class CalendarEventSerializer(serializers.ModelSerializer):
reminders = serializers.StringRelatedField(many=True)
class Meta:
model = CalendarEvent
fields = ('title', 'reminders')
In my view, I do the following:
def test(request):
#...
event = CalendarEvent.objects.filter(id__in = [930, 935])
serializer = CalendarEventSerializer(event, many = True)
print (serializer.data)
#...
When I open the Debug Toolbar, I see that the database hits reminders table twice for each of the calendar events.
The question is, how this behavior could be optimized.
The most straight-forward way would be prefetching the CalendarEventReminders of the CalendarEvents in your view:
# views.py
def get(request):
event = CalendarEvent.objects.filter(id__in = [930, 935]) \
.prefetch_related('reminders')
# ...
This will prefetch all CalendarEventReminders while getting the CalendarEvents.
Note that this will not trigger a sql join like select_related() would do. We can't use select_related() in this case because we're following the relation backwards. :)
Check out the Django Docs regarding prefetch_related.

Resources