How write a Django REST APIView POST request with a condition? - api

I am trying to create a post request for a game api. The game implies that a user can label a picture. A label entered once is a tagging, a label entered twice for the same resource is a tag.
This is how I am trying to create a Tagging so far:
saved_tagging = Tagging.objects.create(user_id=current_user_id,
gameround=gameround,
resource=random_resource,
tag='tag newwww',
created=datetime.now(),
score=score,
origin=origin
)
tagging_serializer = TaggingSerializer(saved_tagging)
At the moment I am getting the ValueError: Cannot assign "'tag newwww'": "Tagging.tag" must be a "Tag" instance.
Is there any way that I can avoid this?
Here are also my models and the relevant serializer.
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def __str__(self):
return self.name or ''
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='tagging')
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
tagging = Tagging.objects.create(**validated_data)
Tag.objects.create(name=tagging, **tag_data)
return tagging
def __str__(self):
return str(self.tag) or ''
serializers.py
class TaggingSerializer(serializers.ModelSerializer):
tag = StringRelatedField()
resource = ResourceSerializer(read_only=True)
gameround = GameroundSerializer(read_only=True)
class Meta:
model = Tagging
fields = ('id', 'tag', 'gameround', 'created', 'score', 'resource', 'origin')
def create(self, validated_data):
return Tagging.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data

tag must be a Tag Instance !
So... you can do this in two ways (in my opinion).
First you can create a Tag object in your view and than pass this object to tag value in your Tagging create method.
Or create a service layer on your app, and create a custom create method for your model. This in my opinion is superior because your a centralizing your rules in one method instead of in one view.
Ex.:
services/tag_service.py
def create(user_id,gameround,resource,tag,...origin):
if not isinstance(tag, Tag):
#create your tag model based on the String passed
your_new_tag_object = Tag.objects.create(tag=tag, ...)
# Here your create others rules too for model creation
# Return your model .create method
return Tagging.objects.create(user_id=user_id,...tag=your_new_tag_object,...)
And Than use this new create method inside your POST serializer.
from services import tag_service
class TaggingSerializer(serializers.ModelSerializer):
# your normal serializer here
def create(self, validated_data):
return tag_service.create(**validated_data)

Related

How to do Django Rest Framework double nested serializer

I'm trying to build an api that have an endpoint which in the POST receives the JSON like bellow:
{
"title":"Quiz 1",
"questions":[
{
"description":"Question 1?",
"answers":[
{
"description":"Answer 1",
"true_or_false":true
},
{
"description":"Answer 2",
"true_or_false":false
}
]
},
{
"description":"Question 2?",
"answers":[
{
"description":"Answer 1",
"true_or_false":true
},
{
"description":"Answer 2",
"true_or_false":false
}
]
}
]
}
But I don't know how to build the serializers for double nested fields, like a list of answers within a question and a list of questions within a quiz. The code I made so far is below:
models.py
from django.db import models
class Quiz(models.Model):
title = models.CharField(max_length=200, blank=False)
def __str__(self):
return self.title
class Question(models.Model):
description = models.CharField(max_length=10000, blank=False)
quiz = models.ForeignKey(Quiz, related_name='questions', on_delete=models.CASCADE, default=None)
def __str__(self):
return self.description
class Answer(models.Model):
question = models.ForeignKey(Question, related_name='answers', on_delete=models.CASCADE)
description = models.CharField(max_length=1000, blank=False)
true_or_false = models.BooleanField(default=False, blank=False)
def __str__(self):
return self.description
views.py
class QuestionViewSet(viewsets.ModelViewSet):
queryset = Question.objects.all()
serializer_class = QuestionSerializer
class AnswerViewSet(viewsets.ModelViewSet):
queryset = Answer.objects.all()
serializer_class = AnswerSerializer
class QuizViewSet(viewsets.ModelViewSet):
queryset = Quiz.objects.all()
serializer_class = QuizSerializer
class OnlyQuizViewSet(generics.ListAPIView):
def get_queryset(self):
queryset = Quiz.objects.filter(id=self.kwargs['pk'])
return queryset
serializer_class = QuizSerializer
serializers.py
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
model = Answer
fields = ['id', 'description', 'true_or_false']
class QuestionSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True)
class Meta:
model = Question
fields = ['id','description','answers']
read_only_fields = ('quiz',)
def create(self, validated_data):
answers_data = validated_data.pop('answers')
question = Question.objects.create(**validated_data)
for answer in answers_data:
Answer.objects.create(question=question, **answer)
return question
class QuizSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Quiz
fields = ['id', 'title', 'questions']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
quiz = Quiz.objects.create(**validated_data)
for questions in questions_data:
Question.objects.create(quiz=quiz, **questions)
return quiz
With the above serializers, I'm getting the error below:
Direct assignment to the reverse side of a related set is prohibited. Use answers.set() instead.
So what's the right way to build double-nested serializers? So far I haven't been able to find anything to help me
I'm gonna assume you get that error when you try posting to the Quiz api, you'll have a problem when you want to create a question
Question.objects.create(quiz=quiz, **questions)
since this question also includes a list of answers that needs to be created first.
Try using the serializer you already have like this
class QuizSerializer(serializers.ModelSerializer):
questions = QuestionSerializer(many=True)
class Meta:
model = Quiz
fields = ['id', 'title', 'questions']
def create(self, validated_data):
questions_data = validated_data.pop('questions')
quiz = Quiz.objects.create(**validated_data)
for questions in questions_data:
question_serializer = QuestionSerializer({**questions, "quiz": quiz}) # might need to send quiz.id instead
question_serializer.is_valid()
question_serializer.save()
return quiz
and update your QuestionSerializer to use AnswerSerializer the same way (or you can create Answers explicitly there)

How to set a fixed value of particular column to user in POST API in Django?

I am using DRF for creating API, using basic APIView. Below are the models and views.
models.py
from django.db import models
from django.utils import timezone
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now)
Now, I need two things in that.
First - I want to set STATUS as 'open' in the database while ordering pizza. User isn't able to see the STATUS column while saving.
Second - I want to get all orders, but it should show STATUS to the user now.
view.py
class PizzaOrderView(APIView):
def get(self, request):
orders = PizzaOrder.objects.all()
serializer = ArticleSerializer(orders, many=True)
return Response({"orders": serializer.data})
def post(self, request):
orders = request.data.get('orders')
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
What should I do to get my all two requirements?
First of all if you want to save status in the database you must include it as one of the fields in your model. I would also suggest adding the choices enumeration so that nothing unwanted can end up in database as a status (note that you can add any other choice besides open and closed):
class PizzaOrder(models.Model):
...
OPEN = 'open'
CLOSED = 'closed'
TYPE_CHOICES = (
(OPEN, 'Open'),
(CLOSED, 'Closed'),
)
status = models.CharField(max_length=120, choices=STATUS_CHOICES, default=OPEN)
Next thing to know is that the way data is presented to the user depends mostly on your serializers (would be useful if you could include those in your question aswell). So for serializing the PizzaOrder data you can use two different serializers (one which includes the status field, and one which does not):
class PizzaOrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = '__all__'
class PizzaOrderWithoutStatusSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ['name', 'size', 'customer_name', 'customer_address', 'ordered_time']
you can user one serializer just just using the one serializer
class PizzaOrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = '__all__'
read_only_fields = ('status',)

How to get an object with a similar tag using django

I have an object (a blogpost) which can have multiple tags in django. I'm trying to get related objects with one or more of these same tags.
For example: You have a blogpost with a few tags, like 'food', 'drinks' and 'restaurants'. When you open this blogpost, there are displayed some 'related' blogposts (meaning they share one or more tags). An example of such a related blogpost would have the tags: 'soda', 'lemonade' and 'drinks'.
Here is my view:
instance = get_object_or_404(Blog, id=id)
tags = instance.tags.values()
related = []
for x in tags: #to put all the tags in an array
related.append(x['name'])
for a in Blog.objects.raw('SELECT * FROM "blog_table" WHERE related in "blog_table"."tags"'):
print (a.name) #this should display the name of all the related blogposts (probably including itself)
Here are my models:
class Tag(models.Model):
name = models.CharField(max_length=500)
number = models.IntegerField(null=True, blank=True)
def __str__(self):
return str(self.number) + ' ' + self.name
class Blog(models.Model):
name = models.CharField(null=False, max_length=500, verbose_name='title of blogpost', unique=True)
body = models.TextField(null=False, verbose_name='body of the blogpost')
tags = models.ManyToManyField(Tag, blank=True, null=True)
def __str__(self):
return self.name
To get the blogs that have similar instance tag, you can do this:
tags = instance.tag.all()
for tag in tags:
print(Blog.objects.filter(tags=tag))

How to modify many to many field in wizard?

I have model contains many to many assign_to_id, I want to modify that field in wizard form through escalte_to method when user trigger escalate button
Model:
class Incident(models.Model):
_name = 'itmangement.incident'
assigned_to_id = fields.Many2one('hr.employee', string="Assigned To",domain="[('department_id', '=', dep_id)]",required=True)
Wizard model
class Escalate(models.TransientModel):
escalated_to = fields.Many2one('hr.employee', string="Escalated To", required=True)
#api.one
def escalate(self):
incident_obj = self.env['itmangement.incident']
record = incident_obj.browse(self._context.get('active_ids'))
record.write({'state': 'escalated'})
class Escalate(models.TransientModel):
escalated_to = fields.Many2one('hr.employee', string="Escalated To", required=True)
#api.one
def escalate(self):
object_id = self.env.context.get('active_id')
for object in self.env['itmangement.incident'].browse(object_id) and self.escalated_to:
object.assigned_to_id = self.escalated_to.id

Filter query with variable list in django

I have a three table model like that :
I want to filter results of a query with a variable list of items for exemple :
listTags = ["landscape","green"]
results = ListTag.objects.filter(tag__name__in=listTags).select_related()
But the result of that query is all the ListTag objects with landscape OR green but what i want it's a list of ListTag objects with landscape AND green
I saw a lot a of answers about that problem but a lot of them use a static list of tags, what i want it's to filter with a variable listtags list
Edit : the model
class Picture(models.Model):
title = models.CharField(max_length=50,null=True, blank=False, verbose_name=('name'))
def __str__(self):
return self.titre
class Tag(models.Model):
name = models.CharField(max_length=50,null=True, blank=False, verbose_name=('name'))
def __str__(self):
return self.name
class ListTags(models.Model):
picture = models.ForeignKey(Picture, blank=False, on_delete=models.CASCADE, related_name='picture')
tag = models.ForeignKey(Tag, blank=False, on_delete=models.CASCADE, related_name='tag')
You can try to use Django Q object.
In your case this could be:
from django.db.models import Q
...
listTags = ["landscape","green"]
query = Q()
for tag in listTags:
query &= Q(tag__name = tag)
results = ListTag.objects.filter(query).select_related()
addition:
if you want just pictures with tags, then you could use many-to-many relationships. But if you want use tags for different types of models, then u need to use generic relations.
In first case models structure could be:
from django.db import models
class Tag(models.Model):
name = models.CharField(max_length=50, null=True, blank=False, verbose_name=('name'))
def __str__(self):
return self.name
class Picture(models.Model):
title = models.CharField(max_length=50, null=True, blank=False, verbose_name=('name'))
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
With m2m relation Q object will not work, so to get all pictures with landscape and green tags you can use filter chaining:
listTags = ["landscape", "green"]
results = models.Picture.objects.all()
for tag in listTags:
results = results.filter(tags__name = tag)
I believe the code below would make this an AND query rather than an OR query:
listTags = ["landscape","green"]
filters = {}
for value in listTags:
filters['tag__name__in'] = value
results = ListTag.objects.filter(**filters)