DRF Serializers .orderitem_set.all() - python-3.x

I was following serializer tutorial and came across some code that i did not understand its function.
def get_order_items(self, obj):
items = obj.orderitem_set.all()
print(obj)
print(items)
return OrderItemSerializer(items, many=True).data
What does the order item function do.
The snippet was from the serializer class below:
class OrderSerializer(serializers.ModelSerializer):
order_items = serializers.SerializerMethodField()
"""
Calculate order_items field
"""
def get_order_items(self, obj):
items = obj.orderitem_set.all()
print(obj)
print(items)
return OrderItemSerializer(items, many=True).data
class Meta:
model = Order
fields = ('phone',
'first_name',
'delivery_date',
'delivery_time',
'address',
'comment',
'payment',
'order_items',)
def create(self, validated_data):
items_data = validated_data.pop('order_items')
order = Order.objects.create(**validated_data)
for item_data in items_data:
OrderItem.objects.create(order=order, **item_data)
return order

The order items functions serializes the OrderItem set, essentially converting in into a dictionary (or json) ready for you frontend to use because of the use of .data.
Grab objects -> Serialize -> Convert into json
So the result of that function will be a format that you can send as a response.

This is a MethodSerializer that will obtain the orderitem_set of the given Order object obj. Then these items run through the OrderItemSerializer and the serializer data is collected and returned with the rest of the data of the Order if the user triggers the corresponding view.
Using a subserializer that way is however not recommended. You can use the subserializer directly as a field as is explained in the documentation [Django-rest-doc]:
class OrderSerializer(serializers.ModelSerializer):
order_items = serializers.OrderItemSerializer(source='orderitem_set', many=True) # 🖘 use the OrderItemSerializer directly
class Meta:
model = Order
fields = ('phone', 'first_name', 'delivery_date', 'delivery_time', 'address', 'comment', 'payment', 'order_items')
def create(self, validated_data):
items_data = validated_data.pop('order_items')
order = Order.objects.create(**validated_data)
for item_data in items_data:
OrderItem.objects.create(order=order, **item_data)
return order

Related

Django Create or update with unique together values

I'm trying to make an attendance system in the frontend I retrieve a list of users which the request.user can take attendance of, I'm using CreateAPIView but this won't get me the desired effect as I want the request.user to be able to toggle between Absent, Present, and on_leave even after the entry has been created on the first request
i have seen questions and answers about create or update here but i couldn't use them so any help would be appriciated
this is my view.py
class AttendanceListCreateAPIView(CreateAPIView):
permission_classes = [IsTeacher_Student]
queryset = Attendance.objects.all()
serializer_class = AttendanceSerializer
def post(self, request, *args, **kwargs):
user = request.user
data = request.data
serializer = AttendanceSerializer(data=data)
if serializer.is_valid():
data_user = serializer.validated_data['user']
## validation
return Response({"message": "Something is wrong, maybe you have already taken attendance for this user"},
status=status.HTTP_400_BAD_REQUEST)
my serializer.py
class AttendanceSerializer(serializers.ModelSerializer):
date = serializers.HiddenField(default=timezone.now)
leave_reason = serializers.CharField(required=False, default="")
class Meta:
model = Attendance
fields = ['user', 'presence', 'leave_reason', 'date']
extra_kwargs = {
'user': {'required': True},
'presence': {'required': True},
'leave_reason': {'required': False},
}
validators = [
UniqueForYearValidator(
queryset=Attendance.objects.all(),
field='user',
date_field='date',
message=("You have already taken the attendance")
)
]
def create(self, validated_data):
instance = Attendance.objects.create(
user=validated_data['user'],
presence=validated_data['presence'],
leave_reason=validated_data['leave_reason'],
date=validated_data['date'],
)
instance.save()
return instance
desired effect
the user fk and date are unique together meaning that if the date isn't unique for the user update it if it is create a new entry

DRF Intermediary Table - POSTing data to the Intermediary Table

I have a many-to-many relationship in my DB design and I am having trouble with the POST aspect. I currently have a table called Loads, Containers, and Container_Loads (this is the intermediary table).
My question is this:
I want to be able to send a POST request into the ContainerLoad intermediary table and just update that table with the values it requires which are: Load ID (PK of the Load table), Container ID (PK of the Container Table) and # of pallets (unique field to the intermediary table). I am able to GET/retrieve the records just fine, but when I try to send a POST request with a payload such as
{
"id":3,
"pallets":"4",
"containerNumberId":5,
"loadNumberId":53
}
(where containerNumberID and loadNumberID are the existing keys in their respective tables), it seems that my code wants to create a whole new Load entry as well (as it asks me for the remaining fields of the Load model), where as I just want to create an entry in the intermediary table without creating a new entry in the Load table.
So for the purpose of my project, a load can be on many containers [imagine that it's split because all of it couldn't fit on one] and a container can belong to many loads.
My models.py looks like this:
class ContainerLoad(models.Model):
id = models.AutoField(primary_key=True)
load_number = models.ForeignKey(Load,on_delete=models.CASCADE)
container_number = models.ForeignKey(Container,on_delete=models.CASCADE)
pallets = models.CharField(blank=True,null=True,default=0,max_length=20)
class Meta:
db_table = 'ContainerLoad'
#load model shortened for brevity
class Load(models.Model):
id = models.AutoField(primary_key=True)
bnsf_container_number = models.ManyToManyField(Container, through='ContainerLoad',through_fields=('load_number','container_number'))
class Meta:
db_table = "Load"
class Container(models.Model):
id = models.AutoField(primary_key=True)
container_number = models.CharField(max_length=15)
in_use = models.BooleanField()
class Meta:
db_table = "Container"
my serializers.py currently looks like this, the commented out section is from me attempting to get the POST to work)
class ContainerLoadSerializer(WritableNestedModelSerializer):
# load_number_id = LoadSerializer(read_only=False)
# container_number_id = ContainerSerializer(read_only=False)
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2
class LoadSerializer(WritableNestedModelSerializer):
primary_driver = DriverSerializer(read_only=False)
second_driver = DriverSerializer(allow_null=True,read_only=False)
third_driver = DriverSerializer(allow_null=True,read_only=False)
bnsf_container_number = ContainerSerializer(read_only=False)
pickup_location = LocationSerializer(read_only=False)
delivery_location = LocationSerializer(read_only=False)
broker = BrokerSerializer(read_only=False)
booked_by = EmployeeSerializer(read_only=False)
class Meta:
model = Load
fields = '__all__'
depth = 1
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class Meta:
model = Container
fields = '__all__'
depth = 1
And finally the views.py
class ContainerLoadViews(APIView):
def get(self, request, id=None):
if id:
container = ContainerLoad.objects.get(id=id)
serializer = ContainerLoadSerializer(container)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
containers = ContainerLoad.objects.all()
serializer = ContainerLoadSerializer(containers, many=True)
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
def post(self, request):
serializer = ContainerLoadSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({"status": "success", "data": serializer.data}, status=status.HTTP_200_OK)
else:
return Response({"status": "Error", "data": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
If you look at the code of the save method of the BaseSerializer class you will see this:
if self.instance is not None:
self.instance = self.update(self.instance, validated_data)
assert self.instance is not None, (
'`update()` did not return an object instance.'
)
else:
self.instance = self.create(validated_data)
assert self.instance is not None, (
'`create()` did not return an object instance.'
)
How you are not passing the instance in the post function:
serializer = ContainerLoadSerializer(data=request.data)
The save is always calling to create. You should do something like.
try:
instance = ContainerLoad.object.get(id=request.data['id'])
except:
instance = None
serializer = ContainerLoadSerializer(instance=instance, data = request.data)
You have depth is set to 2 in your ContainerLoadSerializer Meta class, which is telling the serializer to generate a nested representation of your models.
https://www.django-rest-framework.org/api-guide/serializers/#specifying-nested-serialization
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
If you remove the depth attribute, the serializer should default back to expecting a primary key value, which is your desired behaviour.
Your serializer should look something like this:
class ContainerLoadSerializer(serializers.ModelSerializer):
class Meta:
model = ContainerLoad
fields = "__all__"
The solution to this was that I needed a nested response when reading the data from the ContainerLoad table but a simple write (not nested) function when POSTing the data.
The solution was to use the to_representation and to_internal_value methods (https://www.django-rest-framework.org/api-guide/serializers/#overriding-serialization-and-deserialization-behavior) available within DRF to override the behavior of the serializers. Here is the code that now works for both GET and POST requests and it is no longer asking me for fields related to the Load or Container models when inserting data.
class ContainerSerializer(serializers.ModelSerializer):
container_number = serializers.CharField()
in_use = serializers.BooleanField()
class ContainerFieldSerializer(serializers.Field):
def to_internal_value(self,value):
return Container.objects.get(id=value)
def to_representation(self,instance):
return ContainerSerializer(instance=instance).data
class Meta:
model = Container
fields = '__all__'
depth = 1
I did the same for the Load Serializer.
and then for my ContainerLoad Serializer I just assign the FK fields to the new classes I created:
class ContainerLoadSerializer(serializers.ModelSerializer):
cl_container = ContainerSerializer.ContainerFieldSerializer()
cl_load = LoadSerializer.LoadFieldSerializer()
class Meta:
model = ContainerLoad
fields = "__all__"
depth = 2

Django REST Framework UniqueTogetherValidator fails with FK from kwargs

I've got myself into a trouble with UniqueTogetherValidator.
The problem is that ReviewSerliazer unlike CommentSerializer which is almost identical does unique together validation before actually getting a title value from kwargs and sends back 400 answer with title field being required. I've tried to identify it as a HiddenField, but although serializer validation goes fine within tests, model validation does not. And I receive django.db.utils.IntegrityError: UNIQUE constraint failed: reviews_review.author_id, reviews_review.title_id
Key idea is that title should not be included in serializer response. Making it PrimaryKeyRelatedField and then doing the to_representation seems odd to me.
Any ideas how to fix this without catching exceptions in the viewset, which is obviously wrong?
models.py
class BasePost(models.Model):
text = models.TextField()
author = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='%(class)ss')
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-pub_date', )
abstract = True
def __str__(self):
return self.text[:30]
class Review(BasePost):
score = models.IntegerField(
default=0, validators=[MaxValueValidator(10), MinValueValidator(1)])
title = models.ForeignKey(
Title, on_delete=models.CASCADE, related_name='reviews')
class Meta(BasePost.Meta):
constraints = [
models.UniqueConstraint(
fields=('author', 'title', ), name='unique_title_review')]
class Comment(BasePost):
review = models.ForeignKey(
Review, on_delete=models.CASCADE, related_name='comments')
urls.py
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews', ReviewViewSet, basename='review')
router_v1.register(
r'^titles/(?P<title_id>\d+)/reviews/(?P<review_id>\d+)/comments',
CommentViewSet, basename='comment')
views.py
class ReviewViewSet(BasePostViewSet):
serializer_class = ReviewSerializer
def get_queryset(self):
return self.get_title().reviews.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, title=self.get_title())
def get_title(self, key='title_id'):
return get_object_or_404(Title, id=self.kwargs.get(key))
class CommentViewSet(BasePostViewSet):
serializer_class = CommentSerializer
def get_queryset(self):
return self.get_review().comments.all()
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
def get_review(self, key='review_id'):
return get_object_or_404(Review, id=self.kwargs.get(key))
serializers.py
class ReviewSerializer(BasePostSerializer):
title = serializers.HiddenField(default=None)
class Meta:
model = Review
fields = ('id', 'text', 'author', 'score', 'pub_date', 'title', )
validators = [UniqueTogetherValidator(
queryset=Review.objects.all(), fields=('author', 'title', ))]
class CommentSerializer(BasePostSerializer):
class Meta:
model = Comment
fields = ('id', 'text', 'author', 'pub_date', )
def create(self, validated_data):
try:
review = Review.objects.create(**validated_data)
except IntegrityError:
raise serializers.ValidationError(
{'detail': 'Вы можете оставить только один отзыв.'})
return review
Currently fixed like that but seems wrong to me.
First, I will assume that BasePostViewSet does inherits from CreateModelMixin. My assumption is based on the fact that you are redefining perform_create:
def perform_create(self, serializer):
serializer.save()
According to DRF Documentation on passing additional argumernts to the .save() method:
Any additional keyword arguments will be included in the validated_data argument when .create() or .update() are called.
The original .create() method from the CreateModelMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
It does the validation before calling the .perform_create() method, hence your redefined version of .perform_create:
def perform_create(self, serializer):
serializer.save(author=self.request.user, review=self.get_review())
Gets called after validation, hence, your serializer validator is not checked with your desired arguments.
Now, I think you could achieve this by including your fields in the to_internal_value function data like this (didn't try it out but definitely it runs before validation) (I'm assuming that serializer context is passed accordingly):
def to_internal_value(self, data):
data['author'] = self.context['request'].user
data['review'] = self.context['view'].get_review()
return data
Hopefully this helps.

How to filter Django serializer data?

I'm trying to filter data based on userName in JWT. This is how I've been trying to do it:
views.py:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user)
except ValidationError as v:
print("validation error", v)
return Response(person[0])
This works as I can get the Person data with print("Person: ", person[0]). The return Response(person[0]) however returns an error: TypeError: Object of type Person is not JSON serializable. I guess I could use a serializer class to return a valid JSON, am I right? I have this in my serializers.py:
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Person
fields = '__all__'
I just don't know how to use this with my view. If I use serializer instead of person = Person.objects.filter(userName=request.user), how is the filtering supposed to be done?
Please correct me if I'm not on right track at all.
You can add serializer to view, provide query result to serializer and get serialized data:
class TestView(APIView):
permission_classes = (IsAuthenticated,)
serializer_class = TestSerializer # add serializer
def get(self, request):
token = request.META.get('HTTP_AUTHORIZATION', " ").split(' ')[1]
data = {'token': token}
try:
valid_data = VerifyJSONWebTokenSerializer().validate(data)
user = valid_data['user']
request.user = user
person = Person.objects.filter(userName=request.user).last()
data = self.serializer_class(instance=person).data # serialize query result
except ValidationError as v:
print("validation error", v)
raise v
return Response(data) # return serialized response
Here you can find some examples of using serializer with class based views.
Also note that your Person model might have security-sensitive fields like password, etc, so it's better to specify exact fields you need in serializer rather then use fields = '__all__'.
Are you using Django-Rest-Framework (based on your use of ModelSerializer)?
In pure Django, from the documentation on serializers, you can do something like:
from django.core import serializers
json_response = serializers.serialize("json", person[0])
return Response(json_response)
If you are using Django-Rest-Framework:
return Response(TestSerializer(person[0]).data)

instance expected, got OrderedDict Django Rest Framework writable nested serializers

I am creating a survey kind of app, so i have three models Form, Questiosn, Choices[for multiple choice questions]
I followed this tutorial http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
It works fine for 1 level nested relations, but for 2 levels it gives
TypeError: 'Choice' instance expected, got OrderedDict([(u'title', u'option1')])
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
def create(self, validated_data):
choices_data = validated_data.pop("choices")
question = Question.objects.create(**validated_data)
for choice_data in choices_data:
Choice.objects.create(question=question, **choice_data)
return question
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active', 'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
Question.objects.create(form=form, **question_data)
return form
EDIT
Solved using the manual way, In FormSerializer override the create method,
#transaction.atomic
def create(self, validated_data):
try:
with transaction.atomic():
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
question = Question.objects.create(form=form,
title=question_data['title'],
type=question_data['type'],
required=question_data['required'])
if question.type == Question.RADIO or question.type == Question.CHECKBOX:
choices_data = question_data.pop('choices')
for choice_data in choices_data:
choice = Choice.objects.create(question=question, title=choice_data['title'])
return form
except Exception, e:
raise serializers.ValidationError("Cannot Save Form %s" % e)
I also struggled with this and I believe the proper way to handle this is:
class ChoiceSerializer(serializers.ModelSerializer):
class Meta:
model = Choice
fields = ['title']
class QuestionSerializer(serializers.ModelSerializer):
choices = ChoiceSerializer(many=True, required=False)
class Meta:
model = Question
fields = ['title', 'type', 'required','order','choices']
class FormSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Form
fields = ['title', 'description', 'created', 'active',
'hash','questions']
read_only_fields = ['active','hash']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
form = Form.objects.create(**validated_data)
for question_data in questions_data:
choices_data = question_data.pop('choices')
Question.objects.create(form=form, **question_data)
for choice_data in choices_data:
choice = Choice.objects.create(question=question, **choice_data)
return form
An easy way to screw this up is to not pop choices before creating the Question object. When you do that, you'll get an instance expected, got OrderedDict( 500 error.
Note also that you do not need to define create() on the QuestionSerializer. All child processing is done at the top level.

Resources