How to limit the number of objects given from nested serialization - django-rest-framework

Store has a foreign key to SimilarStore. Normally, there is about a hundred of similar stores in similarstore_set. Is there a way to limit the number of similar stores in similarstore_set when I make API with Django REST Framework?
serializer.py
class SimilarStoreSerializer(ModelSerializer):
class Meta:
model = SimilarStore
fields = ('domain', )
class StoreSerializer(ModelSerializer):
similarstore_set = SimilarStoreSerializer(many=True)
class Meta:
model = Store
fields = '__all__'
UPDATE
The following codes throws 'Store' object has no attribute 'similarstores_set', it actually has similarstore_set, why is it throwing the error?
class StoreSerializer(ModelSerializer):
image_set = ImageSerializer(many=True)
promotion_set = PromotionSerializer(many=True)
similar_stores = SerializerMethodField()
def get_similar_stores(self, obj):
# get 10 similar stores for this store
stores = obj.similarstores_set.all()[:10] <-- This line throws the error
return SimilarStoreSerializer(stores, many=True).data
class Meta:
model = Store
fields = '__all__'

You can use a SerializerMethodField to perform a custom lookup and limit the number of records:
class StoreSerializer(ModelSerializer):
similar_stores = serializers.SerializerMethodField()
def get_similar_stores(self, obj):
stores = obj.similarstore_set.all()[:10] # get 10 similar stores for this store
return SimilarStoreSerializer(stores, many=True).data
class Meta:
model = Store
fields = '__all__'

You could add a serializers.SerializerMethodField() for similarstore_set and define a method that would query the SimilarStore data and set similarstore_set. You could pass the number of elements you want in similarstore_set by passing context to your serializer. see https://www.django-rest-framework.org/api-guide/serializers/#including-extra-context

Related

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)

Manipulate data on serializer before creating model instance in DangoRestFramework

I have three related models as such
Order model
class Order(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
Name = models.CharField(max_length=250)
orderType = models.ForeignKey(OrderType, on_delete=models.CASCADE, null=True)
class Meta:
ordering = ['id']
def __str__(self):
return '{}'.format(self.Name)enter code here
OrderPricing Model
class OrderPricing(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
TotalPrice = models.DecimalField(decimal_places=2, max_digits=10)
#related field
order = models.ForeignKey(Order, on_delete=models.CASCADE, null=True)
class Meta:
ordering = ['order']
def __str__(self):
return self.TotalPrice
OrderType Model
class OrderType(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
Name = models.CharField(max_length = 100)
Premium = models.BooleanField()
class Meta:
ordering = ['id']
Let's ignore the order in which the models appear above.
I have three SerializerModels for each model.
I can crud each model on the BrowsableAPI
Q1:
From the browsableAPI I can create an Order.
I haven't gotten to the 'Writable Nested Serializer' yet and I believe Django has that figured out in their docs through the drf-writable-nested class.
I have two orderTypes
1 = {'Not Premium':'False'} #not Premium
2 = {'Premium':'True'} #Premium
Assume I have a variable order_price = 5 #£5
How can I
Create an order,
If order is premium, then set order_price to 10 #order_price * 2
If order is NOT premium, then set order_price to 5
Create an instance of OrderPricing, that's related to the order. Also, pass the order_price variable to the property TotalPrice when creating the instance
from what I have seen and tried, I can override the Create() on the serializer as such
class OrderSerializer(WritableNestedModelSerializer):
"""OrderSerializer"""
# orderPricing = OrderPricingSerializer(required=False)
class Meta:
model = Order
fields = ('__all__')
def create(self, validated_data):
#create instance of order
#determine of order is premium
typeid = uuid.UUID(validated_data.pop('orderType'))#get FK value
isPremium = OrderType.objects.get(id = str(typeid.id))#determine if **Premium** is True/False
# set/calculate the price of the order
#create a related instance of OrderPricing
Q2
I am aware of GenericViews and the CreateModelMixin, what I don't know is, which is better, overriding the .create() at the serializer or overriding the CreateModelMixin method at the GenericView
Well, where to put business logic is always question hard to answer.
You have multiple places where it can be - view, serializer, model or some other separate module/service.
All have pros and cons- you can find many articles on this topic.
But in your case, I would probably go with perform_create of your view and I would create a method in the serializer which would update the price. If I needed to use the code to update price, I'd move to separate shared module and call it from there.
So let's say you use CreateModelMixin or better ListCreateAPIView
class YourView(ListCreateAPIView):
serializer = OrderSerializer
queryset = your_queryset
def perform_create(self, serializer):
serializer.update_price()
serializer.save()
perform_create is called after data is validated, so you can access the validated data.
update_price is your code where you update the price.
You can argue to move this logic to serializer's create or save method but they do many other things, so unless you need to override these methods for other reasons - you can take advantage of the perform_create method.

Django DRF serializer - inserting data containing foreign key relationships

I have the following models:
class Contact(models.Model):
class Meta:
managed = False
db_table = 'contact'
class ContactPhone(models.Model):
contact = models.ForeignKey(Contact, on_delete = models.CASCADE)
number = models.CharField(max_length = 45)
class Meta:
managed = False
db_table = 'contact_phone'
Also, I have the following serializers:
class ContactSerializer(serializers.ModelSerializer):
server_id = serializers.IntegerField(source='id', read_only=True)
class Meta:
model = Contact
fields = '__all__'
class ContactPhoneSerializer(serializers.ModelSerializer):
class Meta:
model = ContactPhone
fields = '__all__'
Now, I have a view that insert phone numbers for an existing contact.
The input is a json that looks like this:
data = {'contact_id': 12322,
'phones':[{'number': '89120000001'}]}
The view:
def insert_contact_phone(request):
for record in request.data['phones']:
data['contact_id'] = request.data['contact_id']
serializer = ContactPhoneSerializer(data = data)
if serializer.is_valid():
serializer.save()
I end up with the following error:
RelatedObjectDoesNotExist at /contacts/edit ContactPhone has no
contact.
What am I doing wrong?
If you specify __all__ for the fields in your ContactPhoneSerializer, it does not include contact_id.
So the contact_id taken from the json input is not serialized. It is basically ignored and when you try to save and create new ContactPhone - it fails, because it does not have contact's foreign key correctly set.
But simply adding contact_id to the serializer's fields won't solve your problem.
In your view, i recommend you to set the contact instead:
data['contact'] = request.data['contact_id']
and pass this to the ContactPhoneSerializer.

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.

Resources