3đź‘Ť
Have you got difficult property or not?
If not you can rewrite it to queryset like this:
from django.db import models
class UserQueryset(models.Manager):
def get_queryset(self):
return super().get_queryset().annotate(
has_profile=models.Exists(Profile.objects.filter(user_id=models.OuterRef('id')))
)
class User(models.Model):
objects = UserQueryset
class Profile(models.Model):
user = models.OneToOneField(User, related_name='profile')
# When you want to filter by has profile just use it like has field has profile
user_with_profiles = User.objects.filter(has_profile=True)
Maybe it is not what you want, but it can help you in some cases
1đź‘Ť
django-filter
wants and assumes that you are using querysets. Once you take a queryset and change it into a list
, then whatever is downstream needs to be able to handle just a list
or just iterate through the list, which is no longer a queryset.
If you have a django_filters.FilterSet
like:
class FooFilterset(django_filters.FilterSet):
bar = django_filters.Filter('updated', lookup_expr='exact')
my_property_filter = MyPropertyFilter('property')
class Meta:
model = Foo
fields = ('bar', 'my_property_filter')
then you can write MyPropertyFilter
like:
class MyPropertyFilter(django_filters.Filter):
def filter(self, qs, value):
return [row for row in qs if row.baz == value]
At this point, anything downstream of MyProperteyFilter
will have a list.
Note: I believe the order of fields
should have your custom filter, MyPropertyFilter
last, because then it will always be processed after the normal queryset filters.
So, you have just broken the “queryset” API, for certain values of broken. At this point, you’ll have to work through the errors of whatever is downstream. If whatever is after the FilterSet
requires a .count
member, you can change MyPropertyFilter
like:
class MyPropertyFilter(django_filters.Filter):
def filter(self, qs, value):
result = [row for row in qs if row.baz == value]
result.count = len(result)
return result
You’re in uncharted territory, and you’ll have to hack your way through.
Anyways, I’ve done this before and it isn’t horrible. Just take the errors as they come.
- [Django]-What's gettext_lazy on django is for?
- [Django]-Django template tag + template with user.is_authenticated doesn't work
1đź‘Ť
Since filtering by non-field attributes such as property
inevitably converts the QuerySet
to list
(or similar), I like to postpone it and do the filtering on object_list
in get_context_data
method. To keep the filtering logic inside the filterset
class, I use a simple trick. I’ve defined a decorator
def attr_filter(func):
def wrapper(self, queryset, name, value, force=False, *args, **kwargs):
if force:
return func(self, queryset, name, value, *args, **kwargs)
else:
return queryset
return wrapper
which is used on django-filter
non-field filtering methods. Thanks to this decorator, the filtering basically does nothing (or skips) the non-field filtering methods (because of force=False
default value).
Next, I defined a Mixin
to be used in the view
class.
class FilterByAttrsMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
filtered_list = self.filter_qs_by_attributes(self.object_list, self.filterset)
context.update({
'object_list': filtered_list,
})
return context
def filter_qs_by_attributes(self, queryset, filterset_instance):
if hasattr(filterset_instance.form, 'cleaned_data'):
for field_name in filter_instance.filters:
method_name = f'attr_filter_{field_name}'
if hasattr(filterset_instance, method_name):
value = filterset_instance.form.cleaned_data[field_name]
if value:
queryset = getattr(filterset_instance, filter_method_name)(queryset, field_name, value, force=True)
return queryset
It basically just returns to your filterset
and runs all methods called attr_filter_<field_name>
, this time with force=True
.
In summary, you need to:
- Inherit the
FilterByAttrsMixin
in yourview
class - call your filtering method
attr_filter_<field_name>
- use
attr_filter
decorator on the filtering method
Simple example (given that I have model
called MyModel
with property
called is_static
that I want to filter by:
model:
class MyModel(models.Model):
...
@property
def is_static(self):
...
view:
class MyFilterView(FilterByAttrsMixin, django_filters.views.FilterView):
...
filterset_class = MyFiltersetClass
...
filter:
class MyFiltersetClass(django_filters.FilterSet):
is_static = django_filters.BooleanFilter(
method='attr_filter_is_static',
)
class Meta:
model = MyModel
fields = [...]
@attr_filter
def attr_filter_is_static(self, queryset, name, value):
return [instance for instance in queryset if instance.is_static]
- [Django]-Django testing with local settings
- [Django]-Search over multiple fields
- [Django]-How to use default database in django testing
- [Django]-Errno 13 Permission denied Django Upload File
1đź‘Ť
Take a look at django-property-filter package. This is an extension to django-filter and provides functionality to filter querysets by class properties.
Short example from the documentation:
from django_property_filter import PropertyNumberFilter, PropertyFilterSet
class BookFilterSet(PropertyFilterSet):
prop_number = PropertyNumberFilter(field_name='discounted_price', lookup_expr='gte')
class Meta:
model = NumberClass
fields = ['prop_number']
- [Django]-Django objects.get()
- [Django]-Errors When Installing MySQL-python module for Python 2.7
- [Django]-Accessing MultipleChoiceField choices values
- [Django]-How does encoding in email subjects work? (Django/ Python)
- [Django]-Django mock patch doesn't work as I expect