Is is possible to set a default filter in Django models?
Something like:
class MyModel(models.Model):
timestamp = models.DateTimeField(default=datetime.utcnow)
active = models.BooleanField(default=True)
class Meta:
ordering = ['-timestamp']
filtering = [active=True]
You'll have to override the manager:
class MyModelManager(models.Manager):
def get_queryset(self):
return super(MyModelManager, self).get_queryset().filter(active=True)
class MyModel(models.Model):
timestamp = models.DateTimeField(default=datetime.utcnow)
active = models.BooleanField(default=True)
objects = MyModelManager()
class Meta:
ordering = ['-timestamp']
get_queryset was get_query_set before Django 1.6
I was able to accomplish what I wanted by adding managers to the model. I also created an abstract base class to make it easier to add this to other models without having to replicate the same code - here is the modified example:
class MyActiveManager(models.Manager):
def get_queryset(self):
return super(MyModelManager, self).get_queryset().filter(active=True)
class MyInactiveManager(models.Manager):
def get_queryset(self):
return super(MyModelManager, self).get_queryset().filter(active=False)
class AbstractModel(models.Model):
timestamp = models.DateTimeField(default=datetime.utcnow)
active = models.BooleanField(default=True)
objects = MyActiveManager()
objects_inactive = MyInactiveManager()
objects_all = models.Manager()
class Meta:
abstract = True
ordering = ['-timestamp']
class MyModel(AbstractModel):
# Define active-enabled model here
Now, any model I want to have an "active" (and "timestamp" in this example) field can just inherit from the base model. When I use MyModel.objects.all() - I get all objects with active=True - this is especially useful if I already have alot of code using the objects manager. If I want only inactive results, I use MyModel.objects_inactive.all(), and if I want all records regardless of the value of active, I use MyModel.objects_all.all()
Related
I have the following Models:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
class ModelB(models.Model):
some_field_B = models.CharField()
many_to_many_relation = models.ManyToManyField(ModelA)
In admin.py I am using filter_horizontal to edit the ManyToManyField:
class ModelB(admin.ModelAdmin):
model = ModelB
filter_horizontal = ('many_to_many_relation',)
but it shows only some_field_A and I want it to show both fields from ModelA, because the entries in ModelA are unique depending on both fields and as you can see from the picture there are multiple entries with the same value (i.e. some_field_A = EUV) but they have different values for some_other_field_A:
It displays the result of the __str__(…) method you defined in your ModelA, so if you return the value of some_field in the __str__(…) method, then it will return only the data of some_field.
You thus can alter this method and return both fields:
class ModelA(models.Model):
some_field_A = models.CharField()
some_other_field_A = models.CharField()
def __str__(self):
return f'{self.some_field_A} {self.some_other_field_A}'
I'm not sure if this exactly the solution you are looking for but you could override the __str__ method of ModelA to return the information in a single line.
So for example:
class ModelA(models.Model):
first_field = models.CharField(max_length=16)
second_field = models.CharField(max_length=16)
def __str__(self):
return f"{self.first_field} ({self.second_field'})"
Your admin view should then show each object as "foo (bar)"
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.
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
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.
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