How to check if a relation exists? M2M - python

I have models:
class User(models.Model):
...
group = models.ManyToManyField(
Group, related_name="users_group",
)
class Group(models.Model):
....
How to check in serializer is the Group empty (there is no relationship with User)
My version is :
class GroupSerializer(serializers.ModelSerializer):
empty = serializers.SerializerMethodField()
class Meta:
...
def get_empty(self, obj):
return not User.objects.filter(group=obj).exists()
But maybe there is an even better way.

If you define a ManyToManyModel [Django-doc], Django automatically defines one in reverse with the value for the related_name=… parameter [Django-doc] as name, so you can use:
class GroupSerializer(serializers.ModelSerializer):
empty = serializers.SerializerMethodField()
class Meta:
# …
def get_empty(self, obj):
return not group.users_group.exists()
But this is not very efficient if you want to serialize a large number of Groups: for each Group it will make an extra query. You can use a BooleanField:
class GroupSerializer(serializers.ModelSerializer):
empty = serializers.BooleanField()
class Meta:
# …
and then in the APIView or the ViewSet use as QuerySet a QuerySet where you make an annotation with an Exists subquery [Django-doc] to check if there exists a user for that Group:
from django.db.models import Exists, OuterRef
from rest_framework import viewsets
class MyModelViewSet(viewsets.ModelViewSet):
queryset = Group.objects.annotate(
empty=~Exists(
User.objects.filter(group=OuterRef('pk'))
)
)
# …

Related

Django filter by serializer method field

I cant create some logic(for me it interesting). For example i have View like this:
class DucktList(generics.ListAPIView):
serializer_class = DuckSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('test_field',) // i want to create some custom field and filter by it if needed.
serializer:
class DuckSerializer(serializers.ModelSerializer):
test_field = SerializerMethodField() // i want filter by this field!
def get_test_field(self, obj):
return True
class Meta:
......
How i can filter filter_fields with test_field ?
Perhaps you could define your own filter with custom methods and using this library django-filters:
from django_filters import rest_framework as filters
class EventFilter(filters.FilterSet):
finish_on = filters.BooleanFilter(name='finish_on', method='filter_manifestation')
begin_on = filters.BooleanFilter(name='begin_on', method='filter_manifestation')
def filter_manifestation(self, queryset, name, value):
if value is False:
lookup = '__'.join([name, 'gte'])
else:
lookup = '__'.join([name, 'lte'])
qs = queryset.filter(**{lookup: timezone.now()})
return qs
class Meta:
model = Event
fields = [
'finished', 'has_begun'
]
And add this filter to your view:
class ManifestationViewSet(viewsets.ReadOnlyModelViewSet):
...
filter_class = EventFilter
...
You could then adapt your filter's customs methods depending on what you want to do in the related function of your serializer custom field.
You have some snippets on the django-filter library doc about various filters types.

Filter queryset for nested serializer in django rest framework

Here is my view:
class SectorListAPI(generics.ListAPIView):
queryset = SectorModel.objects.all()
serializer_class = SectorSerializer
Here is my serializers:
class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
model = GroupProfile
fields = ('title','slug',)
class DepartmentSerializer(serializers.ModelSerializer):
organizations = OrganizationSerializer(many=True, read_only=True)
class Meta:
model = DepartmentModel
fields = ('title', 'organizations',)
class SectorSerializer(serializers.ModelSerializer):
# title = serializers.CharField()
departments = DepartmentSerializer(many=True, read_only=True)
class Meta:
model = SectorModel
fields = ('title','departments',)
Look, here 'SectorSerializer' is parent 'DepartmentSerializer' is children and 'OrganizationSerializer' is grand children serializer. Now in my view I can easily filter my queryset for 'SectorModel'. But how can i filter on 'GroupProfile' model.
You might want to filter the queryset to ensure that only results relevant to the currently authenticated user making the request are returned.
You can do so by filtering based on the value of request.user.
For example:
from myapp.models import Purchase
from myapp.serializers import PurchaseSerializer
from rest_framework import generics
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
This view should return a list of all the purchases
for the currently authenticated user.
"""
user = self.request.user
return Purchase.objects.filter(purchaser=user)
EDIT
You can subclass the ListSerializer and overwrite the to_representation method.
By default the to_representation method calls data.all() on the nested queryset. So you effectively need to make data = data.filter(**your_filters) before the method is called. Then you need to add your subclassed ListSerializer as the list_serializer_class on the meta of the nested serializer.
1- subclass ListSerializer, overwriting to_representation and then calling super
2- Add subclassed ListSerializer as the meta list_serializer_class on the nested Serializer.
Code relevant to yours:
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(user=self.request.user, edition__hide=False)
return super(FilteredListSerializer, self).to_representation(data)
class OrganizationSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = GroupProfile
fields = ('title','slug',)
class DepartmentSerializer(serializers.ModelSerializer):
organizations = OrganizationSerializer(many=True, read_only=True)
class Meta:
model = DepartmentModel
fields = ('title', 'organizations',)
class SectorSerializer(serializers.ModelSerializer):
# title = serializers.CharField()
departments = DepartmentSerializer(many=True, read_only=True)
class Meta:
model = SectorModel
fields = ('title','departments',)
Thanks to #ans2human for the inspiration behind this answer.
Here's a new approach that is working great for me. I have several Models with is_active = BooleanField(...) that I need to filter out in nested relationships.
NOTE: this solution does not filter out results on non-list fields. for that, you should look to the primary queryset on your View
The core of the work is done by overloading the to_representation() function on a custom ListSerializer, and the many_init on an accompanying custom ModelSerializer:
class FilteredListSerializer(serializers.ListSerializer):
filter_params:dict
def __init__(self, *args, filter_params:dict={"is_active":True}, **kwargs):
super().__init__(*args, **kwargs)
self.filter_params = filter_params
def set_filter(self, **kwargs):
self.filter_params = kwargs
def to_representation(self, data):
data = data.filter(**self.filter_params)
return super().to_representation(data)
class FilteredModelSerializer(serializers.ModelSerializer):
LIST_SERIALIZER_KWARGS = serializers.LIST_SERIALIZER_KWARGS + ("filter_params",)
LIST_ONLY_KWARGS = ('allow_empty', 'filter_params')
#classmethod
def many_init(cls, *args, **kwargs):
list_kwargs = dict()
for arg in cls.LIST_ONLY_KWARGS:
value = kwargs.pop(arg, None)
if value is not None:
list_kwargs[arg] = value
child_serializer = cls(*args, **kwargs, **({"read_only":True} if "read_only" not in kwargs else dict()))
list_kwargs['child'] = child_serializer
list_kwargs.update({
key: value for key, value in kwargs.items()
if key in cls.LIST_SERIALIZER_KWARGS
})
meta = getattr(cls, 'Meta', None)
list_serializer_class = getattr(meta, 'list_serializer_class', FilteredListSerializer)
return list_serializer_class(*args, **list_kwargs)
Then, your custom ModelSerializer for whatever view would instead just extend FilteredModelSerializer instead.
class ChildSerializer(FilteredModelSerializer):
is_active = BooleanField() # not strictly necessary, just for visibilty
... # the rest of your serializer
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
...# the rest of your parent serializer
Now, the children field on the ParentSerializer will filter for is_active = True.
If you have a custom query that you wanted to apply, you can do so by providing a dict of filter params in the standard queryset format:
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True, filter_params={"my_field":my_value, "my_datetime__gte": timezone.now()})
...# the rest of your parent serializer
Alternatively, one could also utilize the set_filter(...) method on the FilteredListSerializer after instantiating the field, like so. This will yield a more familiar format to the original QuerySet.filter(...) style:
class ParentSerializer(serializers.ModelSerializer):
children = ChildSerializer(many=True)
children.set_filter(my_field=my_value, my_datetime__gte=timezone.now())
...# the rest of your parent serializer

Django Rest Ordering custom

I'm using django rest framework and I would like to order with a custom method.
So, when I call this url for example : http://127.0.0.1:8000/api/sets/?ordering=partMissing
It's possible to call a custom method because I would like to order with the number of part missing by snippet. I made count the sum of number of part in the many to many field.
I have very simple POC, that should allow you to implement more sophisticated solution.
views.py:
from rest_framework import viewsets
from ordering_test.models import Test
from ordering_test.ordering import MyCustomOrdering
from ordering_test.serializers import TestSerializer
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
filter_backends = (MyCustomOrdering,)
ordering.py:
from rest_framework.filters import OrderingFilter
class MyCustomOrdering(OrderingFilter):
allowed_custom_filters = ['testMethod']
def get_ordering(self, request, queryset, view):
"""
Ordering is set by a comma delimited ?ordering=... query parameter.
The `ordering` query parameter can be overridden by setting
the `ordering_param` value on the OrderingFilter or by
specifying an `ORDERING_PARAM` value in the API settings.
"""
params = request.query_params.get(self.ordering_param)
if params:
fields = [param.strip() for param in params.split(',')]
# care with that - this will alow only custom ordering!
ordering = [f for f in fields if f in self.allowed_custom_filters]
if ordering:
return ordering
# No ordering was included, or all the ordering fields were invalid
return self.get_default_ordering(view)
def filter_queryset(self, request, queryset, view):
ordering = self.get_ordering(request, queryset, view)
if ordering:
# implement a custom ordering here
ordering = ['-id']
if ordering:
return queryset.order_by(*ordering)
return queryset
The models.py and serializers.py are straightforward, but I will still include them here:
models.py:
from django.db import models
class Test(models.Model):
test = models.CharField(max_length=120)
test1 = models.CharField(max_length=120)
serializers.py:
from rest_framework import serializers
from ordering_test.models import Test
class TestSerializer(serializers.ModelSerializer):
class Meta:
model = Test
fields = ('test', 'test1')
Happy coding!
I think a much easier approach to opalczynski's solution would be to introduce a new field for custom ordering:
from django import forms
import django_filters
from rest_framework import serializers
from .models import MyModel
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('field1',)
class CustomOrderingFilter(django_filters.FilterSet):
order_by = django_filters.BooleanFilter(
widget=forms.HiddenInput(),
method='filter_order_by',
)
class Meta:
model = MyModel
fields = [
'order_by'
]
def filter_order_by(self, queryset, name, value):
if value:
return self.Meta.model.objects.filter(
id__in=queryset.values_list('id', flat=True)
).order_by(value)
return queryset
class TestViewSet(viewsets.ModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer
filter_class = CustomOrderingFilter
Then you can easily order by any field you want like this: example.com/api/mymodel/?order_by=partMissing.
In my example, I used a fixed model field, but you can change the way you order in the filter_order_by method on the CustomOrderingFilter. Just change it to the logic you want, but make sure to use the .filter(=queryset.values_list('id', flat=True)) to ensure that other filters that are set, are being used.
You can use FilterSet to annotate and then OrderingFilter to order accordingly. As an advantage you may use the OrderingField syntax and can still order by multiple fields ad the same time.
/api/?annotate_related={id}&order=subscribed
/api/?annotate_related={id}&order=-subscribed
/api/?annotate_related={id}&order=-subscribed,-modified
FilterSet:
class YourFilterSet(FilterSet):
annotate_related = filters.NumberFilter(method="_annotate_related")
class Meta:
model = Model
def _annotate_related(self, queryset, key, value, *args, **kwargs):
# eg. annotate if user belongs to a certain category
return queryset.annotate(is_subscribed=Case(When(annotate_related__id=value, then=1), output_field=IntegerField(), default=0))
ViewSet:
class YourViewSet(ModelViewSet):
queryset = Model.objects.all()
filterset_class = YourFilterSet
filter_backends = [OrderingFilter, DjangoFilterBackend]
ordering_fields = [
"is_subscribed", # order by annotated field
]

How can i filter queryset in nested serializer in django

I have this code
# Models
class NestedSample(models.Model):
something = models.CharField(max_length=255)
class Sample(models.Model):
thing = models.BooleanField()
nested = models.ForeignKey(NestedSample)
# Serializers
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
In my view I am overrding the queryset for delete=False but it is not applying to nested serializers.
delete=False in queryset will only filter Sample. To filter queryset in nested serializer you can use serializers.ListSerializer like:
class FilterDeleteListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(delete=False)
return super(FilterDeleteListSerializer, self).to_representation(data)
class NestedSampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample
list_serializer_class = FilterDeleteListSerializer
class NestedSample2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = api_models.NestedSample2
list_serializer_class = FilterDeleteListSerializer
class SampleSerializer(serializers.HyperlinkedModelSerializer):
nested = NestedSampleSerializer() # HERE filter delete=false
nested2 = NestedSample2Serializer() # HERE filter deletefalse
class Meta:
model = api_models.Sample
Learn more here
I didn't exactly understand your question, but from what I figured you've got a boolean field in your Model which is set to True if you delete the object instead of actually deleting it from the database (SQL DELETE).
Now coming to your question, if you just want to filter the nested serializer then you could use the SerializerMethodField. You need to specify the method to call as an argument or add a method with the name 'get_' followed by the field name. In this method you can filter the queryset serialize it and return the data of that queryset.
class UserSerializer(serializers.ModelSerializer):
delete_filtered_items = serializers.SerializerMethodField()
class Meta:
model = User
def get_delete_filtered_items(self, obj):
items = Item.objects.filter(user=obj,deleted=False)
serializer = ItemsSerializer(instance=items, many=True)
return serializer.data
The above solution should work for your requirements, but if what you've implemented is similar to a soft delete then it would seem cleaner and more moduler to create a custom model manager.

django rest framework abstract class serializer

I have some models like these:
class TypeBase(models.Model):
name = models.CharField(max_length=20)
class Meta:
abstract=True
class PersonType(TypeBase):
pass
class CompanyType(TypeBase):
pass
Having this, I want to create just one serializer that holds all these field types (serialization, deserialization, update and save).
To be more specific, I want only one serializer (TypeBaseSerializer) that print the Dropdown on the UI, serialize the json response, deserialize it on post and save it for all my based types.
Something like this:
class TypeBaseSerializer(serializers.Serializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
Is it possible?
I think the following approach is more cleaner. You can set "abstract" field to true for the base serializer and add your common logic for all child serializers.
class TypeBaseSerializer(serializers.ModelSerializer):
class Meta:
model = TypeBase
fields = ('id', 'name')
abstract = True
def func(...):
# ... some logic
And then create child serializers and use them for data manipulation.
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = ('id', 'name')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = ('id', 'name')
Now you can use the both of these serializers normally for every model.
But if you really want to have one serializers for both the models, then create a container model and a serializer for him too. That is much cleaner :)
You can't use a ModelSerializer with an abstract base model.
From restframework.serializers:
if model_meta.is_abstract_model(self.Meta.model):
raise ValueError(
'Cannot use ModelSerializer with Abstract Models.'
)
I wrote a serializer_factory function for a similar problem:
from collections import OrderedDict
from restframework.serializers import ModelSerializer
def serializer_factory(mdl, fields=None, **kwargss):
""" Generalized serializer factory to increase DRYness of code.
:param mdl: The model class that should be instanciated
:param fields: the fields that should be exclusively present on the serializer
:param kwargss: optional additional field specifications
:return: An awesome serializer
"""
def _get_declared_fields(attrs):
fields = [(field_name, attrs.pop(field_name))
for field_name, obj in list(attrs.items())
if isinstance(obj, Field)]
fields.sort(key=lambda x: x[1]._creation_counter)
return OrderedDict(fields)
# Create an object that will look like a base serializer
class Base(object):
pass
Base._declared_fields = _get_declared_fields(kwargss)
class MySerializer(Base, ModelSerializer):
class Meta:
model = mdl
if fields:
setattr(Meta, "fields", fields)
return MySerializer
You can then use the factory to produce serializers as needed:
def typebase_serializer_factory(mdl):
myserializer = serializer_factory(
mdl,fields=["id","name"],
#owner=HiddenField(default=CurrentUserDefault()),#Optional additional configuration for subclasses
)
return myserializer
Now instanciate different subclass serializers:
persontypeserializer = typebase_serializer_factory(PersonType)
companytypeserializer = typebase_serializer_factory(CompanyType)
As already mentioned in Sebastian Wozny's answer, you can't use a ModelSerializer with an abstract base model.
Also, there is nothing such as an abstract Serializer, as some other answers have suggested. So setting abstract = True on the Meta class of a serializer will not work.
However you need not use use a ModelSerializer as your base/parent serializer. You can use a Serializer and then take advantage of Django's multiple inheritance. Here is how it works:
class TypeBaseSerializer(serializers.Serializer):
# Need to re-declare fields since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
class Meta:
fields = ['id', 'name']
def someFunction(self):
#... will be available on child classes ...
pass
class PersonTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ['another_field']
class CompanyTypeSerializer(TypeBaseSerializer, serializers.ModelSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields + ['some_other_field']
So now since the fields name and id are declared on the parent class (TypeBaseSerializer), they will be available on PersonTypeSerializer and since this is a child class of ModelSerializer those fields will be populated from the model instance.
You can also use SerializerMethodField on the TypeBaseSerializer, even though it is not a ModelSerializer.
class TypeBaseSerializer(serializers.Serializer):
# you will have to re-declare fields here since this is not a ModelSerializer
name = serializers.CharField()
id = serializers.CharField()
other_field = serializers.SerializerMethodField()
class Meta:
fields = ['id', 'name', 'other_field']
def get_other_field(self, instance):
# will be available on child classes, which are children of ModelSerializers
return instance.other_field
Just iterating a bit over #adki's answer:
it is possible to skip model for TypeBaseSerializer;
derived serializers can refer to TypeBaseSerializer.Meta, so you would change them in a single place.
class TypeBaseSerializer(serializers.Serializer):
class Meta:
fields = ('id', 'name', 'created')
abstract = True
def func(...):
# ... some logic
class PersonTypeSerializer(TypeBaseSerializer):
class Meta:
model = PersonType
fields = TypeBaseSerializer.Meta.fields + ('age', 'date_of_birth')
class CompanyTypeSerializer(TypeBaseSerializer):
class Meta:
model = CompanyType
fields = TypeBaseSerializer.Meta.fields

Categories