How to combine two querysets from two models? Django Rest Framework - python

I have two models
class Answer(models.Model):
answer = models.TextField()
created_at = models.DateTimeField(editable=False, default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
user = models.ForeignKey('users.CustomUser', on_delete=models.PROTECT)
question = models.ForeignKey('Question', on_delete=models.PROTECT)
number_of_points = models.IntegerField(default=0)
moderate_status = models.BooleanField(default=False)
and
class Question(models.Model):
question_subject = models.TextField()
question_text = models.TextField(default=None, null=True, blank=True)
slug = models.SlugField(max_length=128, unique=True, null=False, editable=False)
created_at = models.DateTimeField(editable=False, default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
user = models.ForeignKey('users.CustomUser', on_delete=models.PROTECT)
animal = models.ForeignKey('animals.Animal', on_delete=models.PROTECT)
serializers.py
class QuestionDetailSerializer(serializers.ModelSerializer):
answers = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Question
fields = '__all__'
views.py
class QuestionsDetailView(generics.ListAPIView):
queryset = Question.objects.all()
serializer_class = QuestionsSerializer
def get_queryset(self):
return super().get_queryset().filter(
id=self.kwargs['pk']
)
url.py
path('questions/<int:pk>', QuestionsDetailView.as_view()),
And i want to combine 2 queryset, one being already that filters the question by pk provided in the url, and the other queryset i'd like to give is Answer.objects.all().filter(question__id='pk'). Essentially i want to show all the questions with answers to a particular question.

I strongly suggest to use relationships anywhere you can.
Add related_name to Answer.question in models.py:
class Answer(models.Model):
...
question = models.ForeignKey('Question', on_delete=models.PROTECT, related_name='answers')
If id is enough in serializer, then in serializers.py:
class QuestionDetailSerializer(serializers.ModelSerializer):
# this is not needed anymore: answers = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Question
fields = ['question_subject ', 'question_text', ..., 'answers`]
But you can also create a function of model that you can include in similar way:
class Question(models.Model):
...
def get_answers(self):
return [answer.answer for answer in self.answers.all()] # it will return a list of TextFields of answers
serializers.py:
class QuestionDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['question_subject ', 'question_text', ..., 'get_answers`]

You won't exactly combine 2 queryset from different table. But you can use nested serializer.
class AnswerSerializer(serializers.ModelSerializer):
class Meta:
model = Anwser
fields = ['id', ...]
class QuestionDetailSerializer(serializers.ModelSerializer):
answer_set = AnswerSerializer(many=True, read_only=True)
class Meta:
model = Question
fields = ['id', ..., 'answer_set']
# OR
class QuestionDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['id']
def to_representation(self, instance):
data = super().to_representation(instance)
data['answers'] = AnswerSerializer(many=True, instance=instance.anwser_set.all()).data
return data

Related

How to access model's verbose name in a serializer?

I want to access verbose name of models to put it as a key in serializer. But I can't find a way to do it.
My models are:
class ProductCategory(models.Model):
name = models.CharField(max_length=150, unique=True)
created_at = models.DateTimeField(default=timezone.now)
modified_at = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.name
class DeviceTypeCategory(ProductCategory):
product_category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE,
related_name="device_types")
class Meta:
verbose_name = _("Device type")
verbose_name_plural = _("Device types")
class DeviceBrandCategory(ProductCategory):
product_category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE,
related_name="device_brands")
class PartTypeCategory(ProductCategory):
product_category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name="part_types")
And my serializer:
class ProductCategorySerializer(serializers.ModelSerializer):
device_types = serializers.StringRelatedField(many=True)
device_brands = serializers.StringRelatedField(many=True)
part_types = serializers.StringRelatedField(many=True)
class Meta:
model = ProductCategory
fields = ('name', 'device_types', 'device_brands', 'part_types')
Any suggestions would help. I would also be glad to hear out other ideas on how to create categories model. I've tried django-mptt, but I need product to belong to multiple subcategories. The django-polymorphic-mptt could have help. But I couldn't find proper documentation.
You can do something like the following:
class ProductCategorySerializer(serializers.ModelSerializer):
device_types = serializers.StringRelatedField(many=True)
device_brands = serializers.StringRelatedField(many=True)
part_types = serializers.StringRelatedField(many=True)
plural_name = serializers.SerializerMethodField()
def get_plural_name(self, obj):
return ProductCategory._meta.verbose_name_plural
class Meta:
model = ProductCategory
fields = ('name', 'device_types', 'device_brands', 'part_types', 'plural_name')

Django / DRF - Get field of a related model

everyone.
What I want is get field of a related model by serializer.
I have 2 models:
class Question(models.Model):
question_text = models.CharField(max_length=200)
def __str__(self):
return self.question_text
class Test(models.Model):
test_name = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
question = models.ManyToManyField(Question, related_name='tests')
def __str__(self):
return self.test_name
Why I've used ManyToManyField?
Because I've followed here:
https://medium.com/django-rest/lets-build-a-basic-product-review-backend-with-drf-part-1-652dd9b95485
Now I can do smth like:
But I want get question_text in response.
What I tried:
class TestSerializer(serializers.HyperlinkedModelSerializer):
question_text = serializers.CharField(read_only=True, source="question.question_text")
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question_text']
expandable_fields = {
'question': (QuestionSerializer, {'many': True})
}
But it returned:
I understand, that problem might be in DB relations, but I can't get it.
Thanks in advance!
Use nested serializer:
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = ['question_text']
class TestSerializer(serializers.ModelSerializer):
question = QuestionSerializer(many=True)
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question']
There are two ways to do this I don't know which one is better but this is them
1- create a separate Serializer for question and assgin related_name in models.py to him
class QuestionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Test
fields = "__all__"
class TestSerializer(serializers.HyperlinkedModelSerializer):
tests = QuestionSerializer(many=True) ## related_name
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'tests']
2 - using serializers.SerializerMethodField()
class TestSerializer(serializers.HyperlinkedModelSerializer):
question = serializers.SerializerMethodField()
class Meta:
model = Test
fields = ['pk', 'test_name', 'pub_date', 'question']
def get_question(self, obj):
question = obj.tests.all()
serilaizer = QuestionSerializer(question, many=True)
return serilaizer.data

Django returning chain of 2 different model querysets

Here is my view
serializer_class = SceneSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
scene = Scene.objects.filter(user = self.request.user)
for s in scene:
test = Scene.objects.get(static_id = s.static_id)
lights = test.light.all()
temp = list(chain(scene, lights))
Here are my 2 serializers
class LightSerializer(serializers.ModelSerializer):
class Meta:
model = Light
fields = '__all__'
class SceneSerializer(serializers.ModelSerializer):
class Meta:
model = Scene
fields = '__all__'
Here is my models
class Light(models.Model):
static_id = models.AutoField(primary_key=True)
id = models.CharField(max_length=100, blank=True, null=True)
company = models.CharField(max_length=25, blank=True, null=True)
name = models.CharField(max_length=100, blank=True, null=True)
label = models.CharField(max_length=100, blank=True, null=True)
user = models.ForeignKey(User,on_delete=models.DO_NOTHING, related_name='userLights' )
def __str__(self):
return "{0} - {1}".format(self.user,self.static_id)
class Scene(models.Model):
static_id = models.AutoField(primary_key=True)
color = RGBColorField()
title = models.CharField(max_length=100, blank=True, null=True)
user = models.ForeignKey(User,on_delete=models.DO_NOTHING, related_name='userScenes' )
light = models.ManyToManyField(Light)
def __str__(self):
return "{0} - {1}".format(self.user,self.static_id)
Goal is to return the complete objects of the scene and the lights attached to each scene. the chain is working correctly but when I return the chain I am getting
You can include reverse relationships in serializers by adding the reverse relationship to fields
class SceneSerializer(serializers.ModelSerializer):
class Meta:
model = Scene
fields = ['light', 'static_id', 'color', 'title', 'user']
This will just return a list of ids, to serialize the related objects you can add the related serializer with many=True
class SceneSerializer(serializers.ModelSerializer):
light = LightSerializer(many=True)
class Meta:
model = Scene
fields = ['light', 'static_id', 'color', 'title', 'user']

How to save three related models in one DRF endpoint?

I have 4 related models and I need to implement the functionality to consistently create instances of these models in a database in one post query. For this I use override of the APIView class post method.
models
class VendorContacts(models.Model):
contact_id = models.AutoField(primary_key=True)
vendor = models.OneToOneField('Vendors', on_delete=models.CASCADE)
contact_name = models.CharField(max_length=45, blank=True)
phone = models.CharField(max_length=45, blank=True)
email = models.CharField(max_length=80, blank=True, unique=True)
class Meta:
db_table = 'vendor_contacts'
class VendorModuleNames(models.Model):
vendor = models.OneToOneField('Vendors', on_delete=models.CASCADE, primary_key=True)
module = models.ForeignKey(Modules, models.DO_NOTHING)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'vendor_module_names'
unique_together = (('vendor', 'module'),)
class Vendors(models.Model):
COUNTRY_CHOICES = tuple(COUNTRIES)
vendorid = models.AutoField(primary_key=True)
vendor_name = models.CharField(max_length=45, unique=True)
country = models.CharField(max_length=45, choices=COUNTRY_CHOICES)
nda = models.DateField(blank=True, null=True)
user_id = models.ForeignKey('c_users.CustomUser', on_delete=models.PROTECT)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'vendors'
unique_together = (('vendorid', 'timestamp'),)
class Modules(models.Model):
MODULES_NAME =tuple(MODULES)
mid = models.AutoField(primary_key=True)
module_name = models.CharField(max_length=50, choices=MODULES_NAME)
active = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'modules'
unique_together = (('mid', 'timestamp'),)
serializer.py
class VendorsSerializer(serializers.ModelSerializer):
class Meta:
model = Vendors
fields = ('vendor_name',
'country',
'nda',)
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
'contact_name',
'phone',
'email',)
class VendorModulSerializer(serializers.ModelSerializer):
class Meta:
model = VendorModuleNames
fields = ('module',)
class ModulesSerializer(serializers.ModelSerializer):
class Meta:
model = Modules
fields = ('module_name', )
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
def post(self, request, *args, **kwargs):
vendor_serializer = VendorsSerializer(data=request.data)
vendor_contact_serializer = VendorContactSerializer(data=request.data)
vendor_modules_serializer = VendorModulSerializer(data=request.data)
module_serializer = ModulesSerializer(data=request.data)
try:
vendor_serializer.is_valid(raise_exception=True) \
and vendor_contact_serializer.is_valid(raise_exception=True) \
and vendor_modules_serializer.is_valid(raise_exception=True) \
and module_serializer.is_valid(raise_exception=True)
vendor_serializer.save(user_id=request.user)
# ....
# Some new logic here ????
# ...
except ValidationError:
return Response({"errors": (vendor_serializer.errors,
vendor_contact_serializer.errors,
vendor_modules_serializer.errors
)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
There is no problem saving one Vendor model, but I can't imagine how to save cascading all related models in a single request.
save returns the newly saved object, which you can then pass into the subsequent save() methods:
vendor = vendor_serializer.save(user_id=request.user)
module = module_serializer.save()
vendor_module = vendor_modules_serializer.save(module=module, vendor=vendor)
vendor_contact = vendor_contact_serializer.save(vendor=vendor)

Add Extra fields in serializer from one to one relation models

In this project, I have two models Student and Parent related to each other through one to one field.
In Parent serializer, I want to add Students attributes like age. I am thinking of using SerializerMethodField for both cases is their any better way to do it?
I am not getting the queries on how to get the object attributes and little explanation would be great.
Here what I have done till now.
Models.py
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
batch = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, related_name='students')
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
dob = models.DateField(blank=True, null=True, help_text="Enter in the following format : YYYY-MM-DD")
address = models.TextField(max_length=150, null=True)
age = models.IntegerField(blank=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
#property
def remarks(self):
return self.remark_set.all()
#property
def marks(self):
return self.marks_set.all()
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
class Parent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
child = models.ForeignKey(Student, on_delete=models.CASCADE)
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
address = models.TextField(max_length=150, null=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
Serilaizer.py
class ParentSerializer(serializers.HyperlinkedModelSerializer):
student_age = serializers.SerializerMethodField()
student_batch = serializers.SerializerMethodField()
parent_name = serializers.SerializerMethodField()
class Meta:
model = Parent
fields = "__all__"
def get_student_age(self, obj):
return Parent.objects.get(child__age = self.obj.user.child)
def get_student_batch(self, obj):
return Parent.objects.get(child__bacth = self.obj.user.child)
def get_parent_name(self, user):
return Parent.objects.get(user=self.request.user)
Views.py
class ParentView( mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
queryset = Parent.objects.all()
serializer_class = serializers.ParentSerializer
first way:
from apps.models import Student, parent
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class BasicStudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
class ParentSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(read_only=True,many=False)
child = BasicStudentSerializer(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
you can do this way . its replace a serializer field that you want and if parent have several child then in child's field you have new all child's information as dictionary.
================================================================
second way is use HyperLinkModel .
class ParentSerializer(serializers.ModelSerializer):
user = serializers.HyperlinkedRelatedField(read_only=True,many=False)
child = serializers.HyperlinkedRelatedField(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
but notice that in first way you will have a independent serializer class that every time you need to serialize model class that related to User or Child you can use them simply.

Categories

Resources