How to get only related objects in django serializer with manyToMany? - django

class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = "__all__"
class EmployeeSerializer(serializers.ModelSerializer):
bookings_st = BookingSerializer(many=True, read_only=True)
class Meta:
model = Employee
fields = "__all__"
class ProjectSerializer(serializers.ModelSerializer):
employees = EmployeeSerializer(read_only=True, many=True)
class Meta:
model = Project
fields = "__all__"
class Employee(models.Model):
name = models.CharField(max_length=127)
lastname = models.CharField(max_length=127)
class Project(models.Model):
title = models.CharField(max_length=127)
employees = models.ManyToManyField(Employee,
related_name='employees')
class Booking(models.Model):
start = models.DateField()
end = models.DateField()
employee = models.ForeignKey(Employee,
on_delete=models.CASCADE,
related_name='bookings_st')
project = models.ForeignKey(Project,
on_delete=models.CASCADE,
related_name='bookings_st')
I get nested object, but how to get in Emploee only related to both (project and employee) bookings? Now I just get all bookings that this employee has.
I mean that structure:
project_1:
emploee_1:
[bookings_that_belong_to_THIS_PROJECT]

A possibility here is to leverage a SerializerMethodField together with the serializer's context object:
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = "__all__"
class EmployeeSerializer(serializers.ModelSerializer):
bookings_st = serializers.SerializerMethodField()
def get_bookings_st(self, employee):
project = self.context.get("project")
return BookingSerializer(
employee.bookings_st.filter(project=project),
many=True,
).data
class Meta:
model = Employee
fields = "__all__"
class ProjectSerializer(serializers.ModelSerializer):
employees = serializers.SerializerMethodField()
def get_employees(self, project):
return EmployeeSerializer(
project.employees.all(),
many=True,
context={**self.context, "project": project}
).data
class Meta:
model = Project
fields = "__all__"
This wouldn't be super performant if you're using that when listing projects or when retrieving a specific project with many employees. Depending on your use case - if you're only using it to retrieve a specific project, for example, you could leverage prefetch_related and the Prefetch class to prefetch custom employees + bookings querysets by filtering out the specific project at hand (and you could then use the regular serializers).

Related

Serialization of child models

I have models:
class CommonEditor(models.Model):
def __str__(self):
return 'Common Atributes Mask'
class Color(models.Model):
name = models.CharField(max_length=25)
editor = models.ForeignKey(CommonEditor, on_delete=models.PROTECT, null=True)
So I make serialization this way:
class ColorSerializer(serializers.ModelSerializer):
class Meta:
model = Color
fields = '__all__'
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
And then view:
class CommonAttributeAPIView(generics.ListCreateAPIView):
serializer_class = CommonAttributesSerializer
queryset = CommonEditor.objects.all()
I get only pk of my CommonEditor Model. Why can't i get the full Atributes Mask and how can I fix it? Big thanks!
Default name for reverse foreign key relation is modelname_set or in your case color_set. So try to rename color field to color_set:
class CommonAttributesSerializer(serializers.ModelSerializer):
color_set = ColorSerializer(many=True, read_only=True)
class Meta:
model = CommonEditor
fields = ('pk', 'color_set')
This can also be achieved via SerializerMethodField and can be seen as follow:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = serializers.SerializerMethodField()
class Meta:
model = CommonEditor
fields = ('pk', 'color')
def get_color(self, common_editor):
return ColorSerializer(common_editor.color_set.all(), many=True).data
Documentation: http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
The CommonAttributesSerializer search for a color attribute in CommonEditor's instance, but it couldn't find. In DRF serializer, a parameter called source will says explicitly where to look for the data. So , change the serializer as below:
class CommonAttributesSerializer(serializers.ModelSerializer):
color = ColorSerializer(many=True, read_only=True, <b>source='color_set'</b>)
class Meta:
model = CommonEditor
fields = ('pk', 'color')
Reference : DRF Fields -source

Django - Get parent object with children's object

I'm new to Django so I might miss the answer for this one because of terminology.
I am trying to get parent object with children objects, I've got:
#models.py
class Category(models.Model):
name = models.CharField(max_length=255)
sub_category = models.ForeignKey(SubCategory)
title = models.CharField(max_length=255, null=True)
#serializer.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
#views.py
Product.objects.all().filter(sub_category__category_id=category_id).select_related()
products_serializer = ProductSerializer(products, many=True)
return Response({
'data': products_serializer.data
})
I am trying to get parent category object within the children objects I've already got.
Thanks in advance :)
You already have your parent object, you can access it through each of your objects:
products = Product.objects.all().filter(sub_category__category_id=category_id)
for product in products:
parent = product.parent # You already have it
For better performance change your query to
Product.objects.all().filter(sub_category__category_id=category_id).select_related('parent')
As commented you can do it through serializers:
class ProductItemSerializer(serializers.ModelSerializer):
class Meta:
model = ProductItem
class ProductSerializer(serializers.ModelSerializer):
product_items = ProductItemSerializer(many=True, read_only=True)
class Meta:
model = Product
class CategorySerializer(serializers.ModelSerializer):
products = ProductSerializer(many=True, read_only=True)
class Meta:
model = Category
Now just get categories and products and items will be nested

django rest framework serialize the related fields

models.py
class Category(models.Model):
name = models.CharField(max_length=128)
class Product(models.Model):
category = models.ManyToManyField(Category, related_name="category")
name = models.CharField(max_length=128)
class ProductVariation(models.Model):
product = models.ForeignKey(Product, related_name="product")
name = models.CharField(max_length=128)
serializers.py
class ProductVariantSerializer(serializers.HyperlinkedModelSerializer)
class Meta:
model = ProductVariation
fields = (
"name",
)
class CategoryDetailSerializer(serializers.Modelserializer):
product_variant = PromotionVariantSerializer(many=True)
class Meta:
model = Category
fields =(
"name",
"product_variant" #how can i do this
)
here i want to list all the product variant that belongs to the category.
can i do this way or i want to write methods to get the product variant details
You may need to write serializers for Product and ProductVariation models. You can't display the ProductVariation objects right in the Category model serializer, as there is no direct relation between Category and ProductVariation models. But you could try may be using separate nested serializers for Product and ProductVariation models.
class ProductVariantSerializer(serializers.ModelSerializer):
class Meta:
model = ProductVariation
fields = ("name", )
class ProductSerializer(serializers.ModelSerializer):
variants = ProductVariantSerializer(source='product', many=True, read_only=True)
class Meta:
model = Product
fields = ('name', 'variants')
class CategorySerializer(serializers.ModelSerializer):
products = ProductSerializer(source='category', many=True, read_only=True)
class Meta:
model = Category
fields = ('name', 'products')
You could use the CategorySerializer for nested relationships.

DRF nested serializers - Filtering data on child serializers

I am trying to use nested serializer. How do I use the root serializer to filter data on the grandchild serializer?
School and Program have a many to many relationship So that any school can subscribe to any program. Each school has classes and those classes are part of a program, that's why PClass has foreign keys to both School and program.
When I call my api .../api/school/1 I want to get all the programs that school subscribes to and which classes are available in each program (in that school)
class School(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=40)
slug = models.SlugField(max_length=40, default='', blank=True)
class Program(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50,default='',blank=True, unique=True)
description = models.CharField(max_length=100, blank=True)
school = models.ForeignKey(School, blank=True, null=True, related_name="programs")
class PClass(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50,default='',blank=True)
description = models.CharField(max_length=100)
program = models.ForeignKey(Program, related_name="classes")
school = models.ForeignKey(School, related_name="classes")
and the following serializers:
class SchoolSerializer( serializers.ModelSerializer):
programs = ProgramSerializer(source='get_programas',many=True,read_only=True)
class Meta:
model = School
fields = '__all__'
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
class PClassSerializer(serializers.ModelSerializer):
class Meta:
model = Class
fields = ('name','slug')
class ProgramSerializer(serializers.ModelSerializer):
school = serializers.SlugRelatedField(queryset=School.objects.all(),
slug_field='name',
required=False)
classes = PClassSerializer(many=True,read_only=True)
class Meta:
model = Program
exclude = ('id',)
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
is this possible? or is it a problem with the way I set up my models?
There's 2 ways I know how to do this. The first is you're pretty close already
EDIT: Noticed you're using related names. I've updated the answer for that
class SchoolSerializer( serializers.ModelSerializer):
programas = ProgramSerializer(source='programs',many=True,read_only=True)
For more complex filtering the best way is to use a SerializerMethodField Field. Here's an example.
You'll probably want to also do some pre-fetches in your view to get the queryset to minimize the # of queries.
class SchoolSerializer(serializers.ModelSerializer):
programas = SerializerMethodField(source='get_programas',many=True,read_only=True)
class Meta:
model = Unidade
fields = '__all__'
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
def get_programas(self, obj):
# You can do more complex filtering stuff here.
return ProgramaSerializer(obj.programs.all(), many=True, read_only=True).data
To get the PClasses you'll just need to filter your queryset with
program.classes.filter(school=program.school)
Full example for ProgramSerializer is
class ProgramSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
class Meta:
model = Program
def get_classes(self, obj):
return PClassSerializer(obj.classes.filter(school=obj.school), many=True, read_only=True).data
EDIT 10 or so:
Since you have changed the program -> School from foreignkey to ManyToMany, this changes everything.
For the schoolserializer, you need to use a SerializerMethodField. This way you can pass in extra context to your nested serializer.
class SchoolSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_programs')
class Meta:
model = School
def get_programs(self, obj):
return ProgramSerializer(obj.program_set.all(), many=True, read_only=True, context={ "school": obj }).data
class ProgramSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
class Meta:
model = Program
def get_classes(self, obj):
return PClassSerializer(obj.classes.filter(school=self.context["school"]), many=True, read_only=True).data

How to use a model field for multiple models in Django Rest Framework

I have a model BstUserActionLog with a foreign key to Django model User. I have another model for user profile information, BstUserProfile. When I do serialize BstUserActionLog with ModelSerializer I do have Django User info serialized as it is supposed to be. But I also need to add BstUserProfile serialized using the same user_id used for User model.
How can I serialize BstUserActionLog with model User and BstUserProfile are both serialized?
From my models.py:
class BstUserActionLog(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
bst_action_type = models.ForeignKey(BstActionType)
action_date = models.DateTimeField(auto_now_add=True)
bst_book = models.ForeignKey(BstBook)
new_value_id = models.IntegerField(blank=True, null=True)
old_value_id = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = 'bst_user_action_log'
class BstUserProfile(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, unique=True)
website = models.CharField(max_length=200)
picture = models.CharField(max_length=100)
is_avatar_uploaded = models.BooleanField(default=False)
is_cover_uploaded = models.BooleanField(default=False)
class Meta:
managed = False
db_table = 'bst_user_profile'
app_label = 'bst'
From my serializers.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id','username',)
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3
The key to my solution is SerializerMethodField. With this a new field can be added which is calculated with a method. This method signature contains the object to be serialized. After that a regular method serializer is used to return the serialized object.
From my serializers.py
class BstUserProfileSerializer(serializers.ModelSerializer):
class Meta:
model = BstUserProfile
fields = ('is_avatar_uploaded', 'is_cover_uploaded')
class BstUserActionLogSerializer(serializers.ModelSerializer):
user = UserSerializer()
user_profile = serializers.SerializerMethodField()
def get_user_profile(self, obj):
try:
user_profile = BstUserProfile.objects.get(user_id=obj.user_id)
return BstUserProfileSerializer(user_profile).data
except Exception as e:
return {}
class Meta:
model = BstUserActionLog
fields = ('id', 'user', 'user_profile', 'bst_action_type', 'action_date', 'bst_book', 'new_value_id', 'old_value_id')
depth = 3