Django rest_framework: child object count in serializer - python

I need to count the number of children an object has and return this value in my API via the object serializer. I also need to count a subset of these children objects.
I have a Task object with children Asignees. In my API when I query the tasks I want to have the following data set returned:
[
{ label: "Cross the bridge",
count_assigned: 5,
count_completed: 3 },
{ label: "Build a fire",
count_assigned: 5,
count_completed: 2 }
]
How would I do this? I have found the .annotate() method but that result is not available in the serializer class.
models.py
class Task(models.Model):
label = models.CharField(max_length=255,null=False,blank=False)
class Assignee(models.model):
task = models.ForeignKey(Task, related_name='assignees', on_delete=models.CASCADE, blank=True, null=True)
person = models.ForeignKey(Person, on_delete=models.CASCADE, blank=True, null=True)
completed = models.DateTimeField(null=True,blank=True)
serializers.py
from rest_framework import serializers
from .models import Task, Assignee
from people.serializers import PersonSerializer
class AssigneeSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
person = PersonSerializer(read_only=True)
class Meta:
model = Assignee
fields = ('id','task','person','completed')
read_only_fields = ['id']
class TaskSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
class Meta:
model = Task
fields = ('id', 'label')
read_only_fields = ['id']

The proposed way
class TaskSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField()
count_assigned = serializers.SerializerMethodField()
count_completed = serializers.SerializerMethodField()
class Meta:
model = Task
fields = ('id', 'label', 'count_assigned', 'count_completed')
def get_count_assigned(self, obj):
return obj.assignees.count()
def get_count_completed(self, obj):
return obj.assignees.exclude(completed__isnull=True).count()
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield

If i understand your logic correctly, you can try
in serializers
class TaskSerializer(serializers.ModelSerializer):
count_assigned = serializers.IntegerField(read_only=True)
count_completed = serializers.IntegerField(read_only=True)
then by queryset:
from django.db.models import Count, Case, When, IntegerField
qs = Task.objects.annotate(
count_completed=Count(Case(
When(assignees__completed__isnull=False, then=1),
output_field=IntegerField(),
))
).annotate(count_assigned=Count('assignees'))
serializer = TaskSerializer(qs, many=True)
Or horribly inefficient in models:
from django.utils.functional import cached_property
class Task(models.Model):
#cached_property
def all_assignee(self):
return self.assignees.all()
def count_assigned(self):
return self.all_assignee.count()
def count_completed(self):
return self.all_assignee.filter(completed__isnull=False).count()

Related

Django rest filter by serializermethodfield with custom filter

As declared in question title, i got task to filter results by field not presented in model but calculated by serializer.
The model:
class Recipe(models.Model):
tags = models.ManyToManyField(
Tag,
related_name='recipe_tags'
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='author_recipes'
)
ingredients = models.ManyToManyField(
Ingredient,
related_name='recipe_ingredients'
)
name = models.CharField(max_length=200)
image = models.ImageField()
text = models.TextField()
cooking_time = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1)]
)
class Meta:
ordering = ("-id",)
verbose_name = "Recipe"
verbose_name_plural = "Recipes"
def __str__(self):
return self.name
Here is the view code:
class RecipeViewSet(ModelViewSet):
queryset = Recipe.objects.all()
permission_classes = [IsAdminOrAuthorOrReadOnly, ]
serializer_class = RecipeInSerializer
pagination_class = LimitPageNumberPagination
filter_backends = [DjangoFilterBackend]
filterset_fields = ['tags', ]
filter_class = RecipeFilter
Serializer:
class RecipeOutSerializer(serializers.ModelSerializer):
tags = ManyRelatedField(child_relation=TagSerializer())
author = CustomUserSerializer()
ingredients = serializers.SerializerMethodField()
is_favorite = serializers.SerializerMethodField()
is_in_shopping_cart = serializers.SerializerMethodField()
class Meta:
fields = '__all__'
model = Recipe
def get_ingredients(self, obj):
ingredients = IngredientAmount.objects.filter(recipe=obj)
return GetIngredientSerializer(ingredients, many=True).data
def get_is_favorite(self, obj):
request = self.context.get("request")
if request.user.is_anonymous:
return False
return Favorite.objects.filter(recipe=obj, user=request.user).exists()
def get_is_in_shopping_cart(self, obj):
request = self.context.get("request")
if not request or request.user.is_anonymous:
return False
return ShoppingCart.objects.filter(recipe=obj, user=request.user).exists()
And custom filter code:
class RecipeFilter(rest_framework.FilterSet):
tags = ModelMultipleChoiceFilter(
field_name='tags__slug',
to_field_name="slug",
queryset=Tag.objects.all()
)
favorite = BooleanFilter(field_name='is_favorite', method='filter_favorite')
def filter_favorite(self, queryset, name, value):
return queryset.filter(is_favorite__exact=True)
class Meta:
model = Recipe
fields = ['tags', ]
Target is is_favorited field that return boolean value. I tried writing func in custom filter class that return queryset but didnt work, neither documentation helped me with examples. Hope for your help.
We can use queryset annotate:
from django.db import models
from rest_framework import serializers
class RecipeViewSet(ModelViewSet):
def get_queryset(self):
user = self.request.user
user_id = user.id if not user.is_anonymous else None
return Recipe.objects.all().annotate(
total_favorite=models.Count(
"favorite",
filter=models.Q(favorite__user_id=user_id)
),
is_favorite=models.Case(
models.When(total_favorite__gte=1, then=True),
default=False,
output_field=BooleanField()
)
)
class RecipeOutSerializer(serializers.ModelSerializer)
is_favorite = serializers.BooleanField(read_only=True)
class Meta:
model = Recipe
fields = (
# ...
is_favorite,
)
class RecipeFilter(rest_framework.FilterSet):
favorite = BooleanFilter(field_name='is_favorite')

django custom serializer field not working

I am trying to add an additional field in the serializer but at the moment of adding it I get the following error
error view
During handling of the above exception ('MovieSerializer' object has no attribute 'len_name')
I have seen many posts and all of them are working, what could it be?
this is my code
Model:
from django.db import models
class Movie(models.Model):
title = models.CharField(max_length=255, unique=True)
description = models.TextField()
active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(default=None, null=True)
def __str__(self):
return self.title
serializer:
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
#staticmethod
def create(self, validated_data):
return Movie.objects.create(**validated_data)
#staticmethod
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.active = validated_data.get('active', instance.active)
instance.save()
return instance
#staticmethod
def get_len_name(self, obj):
return len(obj.title)
validators = [MovieValidations.validate_title, MovieValidations.validate_description,
MovieValidations.validate_equals]
everything works fine until I add the serializerMethodField
I think you set the function in the wrong place. The function needs to belong to the MovieSerializer.
from rest_framework import serializers
from watchlist_app.models import Movie
from watchlist_app.api.validations import MovieValidations
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField()
class Meta:
model = Movie
fields = "__all__"
...
def get_len_name(self, obj):
return len(obj.title)
use extra_fields in meta class
class MovieSerializer(serializers.ModelSerializer):
len_name = serializers.SerializerMethodField(method_name='len_name')
class Meta:
model = Movie
fields = "__all__"
extra_fields = ['len_name',]
read_only_fields = ['id', 'len_name', 'created_at']
required_fields = ['title', 'description']
*** your serializer methods in wrong indent,fix it -> Go back one indent and remode staticmethod decorator ***

ManyToManyField does not show

Want to use REST API to populate my tables but my field does not display on the API page.
Models (Series, Roster):
class Series(models.Model):
(...)
def __str__(self):
return self.title
class Roster(models.Model):
(...)
series = models.ManyToManyField(Series)
(...)
def __str__(self):
return self.name
Serializers:
class SeriesSerializer(serializers.ModelSerializer):
class Meta:
model = Series
fields = ('id', 'title', 'icon')
read_only_fields = ('slug',)
class RosterSerializer(serializers.ModelSerializer):
series = SeriesSerializer(many=True, read_only=True)
class Meta:
model = Roster
fields = ('id', 'name', 'number', 'primary_color', 'secondary_color', 'image', 'series')
Views:
class SeriesView(viewsets.ModelViewSet):
serializer_class = SeriesSerializer
queryset = Series.objects.all()
class RosterView(viewsets.ModelViewSet):
serializer_class = RosterSerializer
queryset = Roster.objects.all()
Unsure where I am mistepping here.
So it turns out that all I needed to do was remove
series = SeriesSerializer(many=True, read_only=True)
and adjust my series field to
series = models.ForeignKey(Series, on_delete=models.CASCADE, blank=True, null=True)
No idea why this ended up working though so an explanation would still be cool.

Django Admin filter for another model inside edit form

I face the following problem.
I have 3 different Models:
1.) Testsuite (which contains a list of Tests)
2.) Test (which have different groups. e.g. "production","live", "frontend", "backend" , etc)
3.) Groups (list of all available groups.
The tester needs to create a test suite with a list of tests.
But adding them one by one is not suitable.
The better option is to add them in bulk sorted by groups.
I am looking for 2 solutions.
Include the filter option inside the edit form.
That filter here would be nice
or The other options.
The horizontal list in the edit form
is able to search for the Group tags.
Some code to get a better understanding.
in the forms.py:
<pre>
class TestSuiteForm(forms.ModelForm):
class Meta:
model = TestSuiteModel
fields = ('name','testcases' , 'nutzer' )
widgets = {
'testcases': autocomplete.ModelSelect2Multiple(
'Testcase_autocomplete'
)
}
class TestCaseForm(forms.ModelForm):
class Meta:
model = TestCaseModel
fields = ('name', 'testsuite' , 'gruppen' , 'portal' )
widgets = {
'testsuite': autocomplete.ModelSelect2Multiple(
'Testsuite_autocomplete'
),
'gruppen': autocomplete.ModelSelect2Multiple(
'Gruppen_autocomplete'
),
}
class GroupForm(forms.ModelForm):
class Meta:
model = GroupModel
fields = ('name', 'testcases' )
widgets = {
'testcases': autocomplete.ModelSelect2Multiple(
'Testcase_autocomplete'
)
}
</pre>
the admin.py
<pre>
class TestSuiteFormAdmin(admin.ModelAdmin):
search_fields = ('name',)
form = TestSuiteForm
list_filter = ['name']
class TestCaseAdmin(admin.ModelAdmin):
form = TestCaseForm
list_filter = ['gruppen', ]
list_display = ['name', ]
search_fields = ('name',)
class GroupAdmin(admin.ModelAdmin):
form = GroupForm
list_filter = ['name']
</pre>
and models.py
<pre>
class TestCaseModel(models.Model):
#id = models.CharField(primary_key=True, max_length=50)
name = models.CharField(max_length=50)
gruppen = models.ManyToManyField('GroupModel' , blank=True)
testsuite = models.ManyToManyField('TestSuiteModel' , blank=True)
def __str__(self):
return self.name
class Meta:
db_table = "Testcase"
verbose_name = 'Testcase'
verbose_name_plural = 'Testcases'
#python_2_unicode_compatible
class TestSuiteModel(models.Model):
name = models.CharField(max_length=200)
testcases = models.ManyToManyField('TestCaseModel' , blank=True)
nutzer = models.CharField(max_length=200, blank=True)
def __str__(self):
return self.name
class Meta:
db_table = "Testsuite"
verbose_name = 'Testsuite'
verbose_name_plural = 'Testsuits'
class GroupModel(models.Model):
name = models.CharField(max_length=50)
testcases = models.ManyToManyField('TestCaseModel', blank=True)
def __str__(self):
return self.name
</pre>
This was one solution I found. but that does not work with many to many Fields.
from pprint import pprint
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
class CategoryListFilter(admin.SimpleListFilter):
title = _('TestCaseModel')
parameter_name = 'testcasemodel'
def lookups(self, request, model_admin):
categories = TestCaseModel.objects.all()
for obj in categories:
pprint(vars(obj))
#filter_objects = TestCaseModel.objects.filter(Some_attribut = "some_name")
#filter_objects = TestCaseModel.objects.filter(group = "backend")
def queryset(self, request, queryset):
if self.value():
return queryset.filter(testcasemodel__id=self.value())
class TAdmin(admin.ModelAdmin):
list_filter = (CategoryListFilter,)
For many-to-many relations, you get a none value and there you are not able to filter for that.

Django Python 'Customer' object is not iterable

from django.db import models
class Customer(models.Model):
cust_firstname=models.TextField(max_length=50)
cust_lastname=models.TextField(max_length=50)
cust_company=models.TextField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
cust_contact_number = models.IntegerField()
cust_email = models.TextField(max_length=100)
cust_address=models.TextField(max_length=200,default=None)
class Employee(models.Model):
employee_firstname = models.TextField(max_length=50)
employee_lastname = models.TextField(max_length=50)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
employee_contact_number = models.IntegerField()
employee_email = models.TextField(max_length=100)
employee_designation = models.TextField(max_length=50)
employee_address=models.TextField(max_length=200, default=None)
class ProjectDetails(models.Model):
customer = models.ForeignKey(Customer)
employee=models.ForeignKey(Employee)
project_name = models.TextField(max_length=50)
project_startdate = models.DateTimeField(auto_now=False, default=None)
project_status = models.TextField(default="Initial")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
The above code is my model declaration
and my Serializer class is
from ProjectTemplate.models import Customer, Employee, ProjectDetails
from rest_framework import serializers
class CustomerSerializers(serializers.ModelSerializer):
class Meta:
model=Customer
fields = ('id','cust_firstname','cust_lastname','cust_company','created_at','updated_at','cust_contact','cust_email','cust_address')
read_only_fields = ('created_at', 'updated_at')
class EmployeeSerializer(serializers.ModelSerializer):
class Meta:
model=Employee
fields = ('id','employee_firstname','employee_lastname','created_at','updated_at','employee_contact','employee_email','employee_designation','employee_address')
read_only_fields = ('created_at', 'updated_at')
class ProjectDetailsSerializer(serializers.ModelSerializer):
customer = CustomerSerializers(many=True, read_only=True)
employee = EmployeeSerializer(many=True, read_only=True)
class Meta:
model = ProjectDetails
fields = ('id','project_name','project_startdate','created_at','updated_at','project_status','customer','employee')
read_only_fields = ('created_at', 'updated_at')
In my view i have the below code
def get(self, request, format=None):
queryset = ProjectDetails.objects.all()
projectid = self.request.query_params.get('pk', None)
if projectid is not None:
queryset = queryset.get(id=projectid)
serializer = ProjectDetailsSerializer(queryset, many=False)
return Response(serializer.data)
else:
serializer = ProjectDetailsSerializer(queryset, many=True)
return Response(serializer.data)
And my URL for the above view is
url(r'^api/v1/projects/$', ProjectListView.as_view()),
And when i try to access the URL on my Browser i get TypeError. Im trying to get all the Customers and Employees who belong to a project. But it fails can any one help me to fix this.
I'm trying to get all the Customers and Employees who belong to a project.
I am not sure what do you want to achieve here because looking at your model, an instance of ProjectDetail will only have one customer and one employee:
class ProjectDetails(models.Model):
customer = models.ForeignKey(Customer)
employee=models.ForeignKey(Employee)
Considering this, using many=True doesn't make any sense in the serializer. So this is what causing the error:
class ProjectDetailsSerializer(serializers.ModelSerializer):
customer = CustomerSerializers(many=True, read_only=True)
employee = EmployeeSerializer(many=True, read_only=True)
UPDATE: To show a specific field from the related object:
Based on the comments in the answer the OP want to show the name of customer or employee instead of id.
It can be achieved using a SlugRelatedField:
class ProjectDetailsSerializer(serializers.ModelSerializer):
customer = serializers.SlugRelatedField(slug_field='cust_firstname', read_only=True)
employee = serializers.SlugRelatedField(slug_field='employee_firstname ', read_only=True)
# other fields
Do note that using SlugRelatedField you can get only one field as in the example above you would get firstname for both.

Categories