Manipulate data on serializer before creating model instance in DangoRestFramework - django-rest-framework

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.

Related

Django Rest Framework - Updating a ForeignKey Field entry in the view

In my Django Rest Framework project, I have a ForeignKey relationship between two models:
class Book(models.Model):
...
category = models.ForeignKey(Category, on_delete=models.CASCADE, blank=True, null=True)
...
class Category(models.Model):
name = models.CharField(max_length=100, blank=False)
As you can see, a Book can belong to a Category but it does not have to. That means the 'category' field could be null.
So, in my views.py, any Book instance can be updated/patched if the user wants to assign a certain Book to a particular Category. That views.py update method looks like this:
class UpdateBooksCategory(generics.GenericAPIView):
'''
Class-based view to update the 'category' field of a Book instance.
'''
serializer_class = BookSerializer
permission_classes = [IsAuthenticated]
def patch(self, request,*args, **kwargs):
# get the Book instance first
book = Book.objects.get(pk=request.data.get('bookId'))
# if it is not assigned to a Category, then assign it
if book and not book.category:
book.category = Category.objects.get(name=request.data.get('categoryName'))
book.save()
serializer = self.get_serializer(book, context={"request": request})
return Response(serializer.data)
# otherwise, return a generic response
return Response({'response': "You have already put the selected Book in a Category."})
If you can see, first I get the Book instance that the user wants to update by using the Book's ID. If its Category field is not already filled, I get a Category instance using the given category name and assign it.
For the sake of completeness, here are my serializer classes:
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name']
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', /*some other fields*/,..., 'category']
So, finally my question: I wanted to know if this is the preferred way of updating a ForeingKey field like this? I mean looking at the UpdateBooksCategory class-based view, is this the right way of doing it? The code works ( I tested it with PostMan) but since I am new to DRF I wanted to know if such an updating process is correct.
You can change your BookSerializer:
class BookSerializer(serializers.ModelSerializer):
category_id = serializers.IntegerField(write_only=True)
category = CategorySerializer(read_only=True)
class Meta:
model = Book
fields = [
'id',
# some other fields,
'category',
'category_id',
]
category will be a nested data that is read only, then setting the category will be by including the category_id in your requests.

Django REST: Exclude some fields and filter a nested serializer

I am trying to exclude the field "prospect" from the Appointment serializer, which is nested under the Unit Serializer.
As you can see below, due to some data manipulation on the Appointment queryset, I decided to use a SerializerMethodField to define the Appointment serializer. However, I am not sure how to exclude fields when using a SerializerMethodField.
Alternatively, I could use a ModelSerializer for the Appointment, which allows me to define which fields to include, but then I would not be able to manipulate the data the way I want.
Note sure what to do.
Models
class Appointment(models.Model):
appointment_time = models.DateTimeField()
unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
staff = models.ForeignKey(Staff, on_delete=models.CASCADE)
prospect = models.ForeignKey(Prospect, on_delete=models.CASCADE)
Serializers
class UnitSerializer(serializers.ModelSerializer):
availability = SerializerMethodField()
manager = ManagerSerializer()
instruction = InstructionSerializer(source='instruction_set', many=True)
appointment = SerializerMethodField()
class Meta:
model = Unit
fields = ['id', 'address', 'manager', 'availability', 'instruction', 'appointment']
def get_availability(self, instance):
queryset = instance.availability_set.order_by('start_time')
return AvailabilitySerializer(queryset, many=True).data
def get_appointment(self, instance):
start_buffer = 0.5 ## How many hours from now do we start displaying appointments in the queue
end_buffer = 72 ## How many hours from now do we stop displaying appointments in the queue
start_cutoff = datetime.now() + timedelta(hours=start_buffer)
end_cutoff = datetime.now() + timedelta(hours=end_buffer)
queryset = instance.appointment_set.exclude(appointment_time__lt=start_cutoff).exclude(appointment_time__gt=end_cutoff).order_by('appointment_time')
return AppointmentSerializer(queryset, many=True).data
JSON Object Representation

How to save partial data in a separate model and then query checks if that data exists?

My data consists of some products, which are defined in FdProduct model:
# models.py:
class FdProduct(models.Model):
product_id = models.CharField(max_length=10, primary_key=True)
name = models.CharField(max_length=40)
active = models.BooleanField(default=True)
def __str__(self):
return self.product_id
# serializers.py:
class FdProductSerializer(serializers.ModelSerializer):
product_id = serializers.RegexField(regex='^\d{3}\.\d{6}$', max_length=10, min_length=10, allow_blank=False)
name = serializers.CharField(min_length=6, max_length=50, allow_blank=False)
class Meta:
model = FdProduct
fields = '__all__'
For each existing product I can prepare a configuration and save it using a model called SavedConfiguration:
# models:
class SavedConfiguration(models.Model):
saved_conf_id = models.CharField(max_length=13, primary_key=True)
saved_config = models.TextField()
product = models.ForeignKey(FdProduct, on_delete=models.CASCADE, default=0)
session_id = models.CharField(max_length=40, default=0)
creation_date = models.DateTimeField(auto_now=False, auto_now_add=True)
def __str__(self):
return str(self.saved_conf_id)
# serializers.py:
class SavedConfigurationSerializer(serializers.ModelSerializer):
saved_conf_id = serializers.RegexField(regex='^sc\d{2}', allow_blank=False)
saved_config = serializers.CharField(min_length=6)
session_id = serializers.RegexField(regex='^se\d{2}', allow_blank=False)
class Meta:
model = SavedConfiguration
fields = '__all__'
By connecting the SavedConfiguration with FdProduct model with use of ForeignKey I ensure that a product exists in the database when I configure it and want to save the configuration.
I'd like to introduce two things more: the first one would be a model for storing just a product_id and an array containing all saved_conf_ids for that product:
# models.py:
class ConfigOptions(models.Model):
product_id = ...
saved_conf_id = [...]
For example, if I configured a couple of times two products, Product One and Product Three, I may have data like this:
- Product One:
- C_ID_0023
- C_ID_0025
- C_ID_0032
- Product Three:
- C_ID_0149
- C_ID_0273
My question now is, how to construct such model and serializer for which records are created (copied) into ConfigOptions model table each time SavedConfiguration is saved?
Second question: I'm thinking about creating another model, say ConfigPresenceCheck, which would receive POST requests and based on that would check if saved product configurations exist (so, fetching them from ConfigOptions or returning 404), and if they exist, would return them together with all parameters from SavedConfiguration (e.g. saved_config, session_id, etc.).
Please give me directions how to build such models. I'd also appreciate some good tutorials related to
constructing Django models.
I think you should use many to many fields instead of foreign key, from my understanding many products can have many saved configurations and vice versa, at database level many to many fields are stored by creating a table any way.

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.

Resources