Test a custom django filter - django

Written a filter that is functioning as it should
class ExternalProductActiveStatusFilter(admin.SimpleListFilter):
title = "EP Active status"
parameter_name = "ep active status"
def lookups(self, request, model_admin):
return [
("active", "Active"),
("inactive", "Inactive"),
]
def queryset(self, request, queryset):
if self.value() == "active":
return queryset.distinct().filter(external_products__active=True)
if self.value() == "inactive":
return queryset.exclude(external_products__active=True)
Now I want to test it but can't get it to work.
Looked into old SO questions but the solutions do not do the trick. These ones in particular, Test a custom filter in admin.py(7 years old) and Django filter testing(5 years old)
My test as of right now
def test_externalproductactivestatusfilter_active(self):
self.product1 = baker.make(
"products.Product", id=uuid4(), title="Active product"
)
self.product2 = baker.make(
"products.Product", id=uuid4(), title="Inactive product"
)
self.external_product1 = baker.make(
"external_products.ExternalProduct",
internal_product=self.product1,
active=True,
)
self.external_product2 = baker.make(
"external_products.ExternalProduct",
internal_product=self.product2,
active=False,
)
request = MockRequest()
f = ExternalProductActiveStatusFilter(
None, {"active": "Active"}, Product, ProductAdmin
)
result = f.queryset(request, Product.objects.all())
print(result)
result is None

Of corse None: You have two return in if, and nothing else. IF filter not set - you receive None.
In your case:
def queryset(self, request, queryset):
query = Q()
if self.value() == "active":
query = Q(external_products__active=True)
if self.value() == "inactive":
query = Q(external_products__active=False)
return queryset.filter(query)

Related

admin.SimpleListFilter with related_query_name to filter occurances

I try to filter items in a admin list:
class Product(models.Model):
name = models.CharField("name", max_length = 128)
def associated_stores(self):
stores = set([f"""{s.number}""" for sin self.store_name.all()])
if len(stores) == 0:
return None
return ", ".join(stores)
class Store(models.Model):
products = models.ManyToManyField(Product, related_name = "%(class)s_name", related_query_name = "product_store_qs", blank = True)
number = ...
Now I want to implement a SimpleListFilter that can filter in the products list for products that are available in no stores, or show the store names:
class AssociatedStoresFilter(admin.SimpleListFilter):
title = "associated stores"
parameter_name = "assoc_stores"
def lookups(self, request, model_admin):
return [(True, "yes"), (False, "no")]
def queryset(self, request, queryset):
qs = queryset.annotate(assoc_stores = Case(When(product_store_qs__isnull = False, then = 1), default = 0))
if self.value() == "False":
return qs.filter(assoc_stores = 0)
elif self.value() == "True":
return qs.filter(assoc_stores = 1)
else:
return queryset
The filter for no stores have the product works, but the one where only products should be shown that are available in a store shows one entry for every store of a product (with all stores) instead of only one entry (with all stores) only.
How do I cut down my results to hold every product only once after filtering?
edit: I know I can solve my problem using Count (but I want to understand why the other code does not work, or how to do it smarter):
def queryset(self, request, queryset):
qs = queryset.annotate(assoc_stores = Count("product_store_qs"))
if self.value() == "False":
return qs.filter(assoc_stores = 0)
elif self.value() == "True":
return qs.filter(assoc_stores__gte 1)
else:
return queryset

Django custom SimpleListFilter

I have this two models:
class Intervencao(models.Model):
.......
class Alvaras(models.Model):
intervencao = models.ForeignKey(Intervencao, related_name='IntervencaoObjects2',on_delete=models.CASCADE)
data_alv = models.DateField(blank=True,null=True,verbose_name="Data do alvaras")
I want to add a custom filter in my Intervencao.Admin, that do a query to the last record i have in Alvaras and check if the field data_alv it is empty or not. I already got it working, but i want only the last record.
class Dataalv(admin.SimpleListFilter):
title = ('data de alvaras')
parameter_name = 'data_alv'
def lookups(self, request, model_admin):
return (
('yes', 'yes'),
('no', 'no')
)
def queryset(self, request, queryset):
value = self.value()
if value == 'yes':
return queryset.filter(IntervencaoObjects2__data_alv__isnull=False)
elif value == "no":
return queryset.filter(IntervencaoObjects2__data_alv__isnull=True)
class IntervencaoAdmin(admin.ModelAdmin):
list_filter = Dataalv,
Here is a solution using Subquery:
from django.db.models import OuterRef, Subquery
class Dataalv(admin.SimpleListFilter):
title = ('data de alvaras')
parameter_name = 'data_alv'
def lookups(self, request, model_admin):
return (
('yes', 'yes'),
('no', 'no')
)
def queryset(self, request, queryset):
value = self.value()
qs = queryset.annotate(
data_alv=Subquery(
(Alvaras.objects
.filter(intervencao_id=OuterRef('id'))
.order_by('-id')
.values('data_alv')[:1]
)
)
)
if value == 'yes':
return qs.filter(data_alv__isnull=False)
if value == "no":
return qs.filter(data_alv__isnull=True)

Django admin list_filter - filter field by is empty (None or empty string "")

In models.py I have:
class User(modals.Model):
name = models.CharField(max_length=255)
image = models.ImageField(blank=True, null=True)
And in admin.py:
class UserAdmin(admin.ModelAdmin):
list_filter = ['image']
admin.site.register(User, UserAdmin)
I just want to filter Users by have image or not (null or empty string)
But django shows filter by image urls =)
Is there a way to make list_filter = ['image'] behave like boolean field?
Big thx for advices!
admin.py
class ImageListFilter(admin.SimpleListFilter):
title = _('Has photo')
parameter_name = 'has_photo'
def lookups(self, request, model_admin):
return (
('yes', _('Yes')),
('no', _('No')),
)
def queryset(self, request, queryset):
if self.value() == 'yes':
return queryset.filter(image__isnull=False).exclude(image='')
if self.value() == 'no':
return queryset.filter(Q(image__isnull=True) | Q(image__exact=''))
class UserAdmin(admin.ModelAdmin):
list_filter = [ImageListFilter]
As of Django 3.1, you can use EmptyFieldListFilter as follows:
list_filter = (
("my_fk_field", admin.EmptyFieldListFilter),
)
See the docs for details. See the release notes as well.
The code is available here, if you need to customize it or backport it.
Based on MaxCore's answer, I created customised class that I can use to modify title and parameter name:
from django.contrib import admin
from django.utils.translation import ugettext_lazy as _
from django.db.models import Q
class NotNullFilter(admin.SimpleListFilter):
title = _('Filter title not set')
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', _('Not empty only')),
('null', _('Empty only')),
)
def queryset(self, request, queryset):
if self.value() == 'not_null':
is_null_false = {
self.parameter_name + "__isnull": False
}
exclude = {
self.parameter_name: ""
}
return queryset.filter(**is_null_false).exclude(**exclude)
if self.value() == 'null':
is_null_true = {
self.parameter_name + "__isnull": True
}
param_exact = {
self.parameter_name + "__exact": ""
}
return queryset.filter(Q(**is_null_true) | Q(**param_exact))
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
K.H.'s approach of a base class that could be reused easily was really helpful for me. I couldn't get the written example to work, but with a small tweak it worked perfectly (Python 2.7, Django 1.10) to achieve this.
from django.contrib import admin
class NotNullFilter(admin.SimpleListFilter):
title = 'Filter title not set'
parameter_name = 'parameter name not set'
def lookups(self, request, model_admin):
return (
('not_null', 'Not empty only'),
('null', 'Empty only'),
)
def queryset(self, request, queryset):
filter_string = self.parameter_name + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
class YoutubeNotNullFilter(NotNullFilter):
title = "Youtube"
parameter_name = "youtube_videoid"
class SomeVideoClass(admin.ModelAdmin):
...
list_filter = [YouTubeNotNullFilter, ...]
A bit more flexible version based on MaxCore's answer, which creates new classes on-the-fly
def by_null_filter(attr, name, null_label='is Null', non_null_label='not Null', bool_dt=False, bool_value=False):
class ByNullFilter(admin.SimpleListFilter):
"""List display filter to show null/not null values"""
parameter_name = attr
title = name
def lookups(self, request, model_admin):
if bool_dt:
label_null = 'not %s' % attr
label_non_null = attr
elif bool_value:
label_null = 'no'
label_non_null = 'yes'
else:
label_null = null_label
label_non_null = non_null_label
return (
('not_null', label_non_null),
('null', label_null)
)
def queryset(self, request, queryset):
filter_string = attr + '__isnull'
if self.value() == 'not_null':
is_null_false = {
filter_string: False
}
return queryset.filter(**is_null_false)
if self.value() == 'null':
is_null_true = {
filter_string: True
}
return queryset.filter(**is_null_true)
return ByNullFilter
Usage
if you have processed DateTime field and you want to filter on it - does it have value or null
# with default filter labels (`not Null`, `is Null`)
list_filter = (by_null_filter('processed', 'Processed'), )
# Processed header with labels based on field name (`processed`, `not processed`)
list_filter = (by_null_filter('processed', 'Processed', bool_dt=True), )
# Paid header filter with labels based on bool(end_time) (`yes`, `no`)
list_filter = (by_null_filter('end_time', 'Paid', bool_value=True), )

Django admin, how to keep the query parameters on the next filter

I have the following ListFilter:
class ProductCountryListFilter(admin.SimpleListFilter):
title = _('country')
parameter_name = 'country'
def lookups(self, request, model_admin):
unused = request.GET.get('unused', 0)
_countries = Product.objects.values_list('loc_country_code', flat=True).order_by(
'loc_country_code').distinct()
return [(c, countries[c]) for c in _countries]
def queryset(self, request, queryset):
country = request.GET.get('country')
return queryset if country is None else queryset.filter(
loc_country_code=country
)
The output in html for this is:
Deutschland
I want to add to the href the parameter 'unused', so basically I want this result:
Deutschland
def choices(self, cl):
yield {
'selected': self.value() is None,
'query_string': cl.get_query_string({}, [self.parameter_name]),
'display': _('All'),
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == force_text(lookup),
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
This method from SimpleListFilter is in charge of generating the urls.
This is the html for filters:
https://github.com/django/django/blob/master/django/contrib/admin/templates/admin/filter.html

select related data for custom list filter

class Answer(models.Model):
u_id=models.ForeignKey(User)
e_id=models.ForeignKey(Event)
c_desc=models.TextField(verbose_name="Candidate Description")
c_vote_count=models.IntegerField(default=0,verbose_name="Candidate vote count")
c_logo=models.ImageField(upload_to="candidateimages",null=True,blank=True)
class Meta:
verbose_name="Candidate"
verbose_name_plural = "Candidates"
unique_together=("e_id","u_id")
class UserRequestPool(models.Model):
u_id=models.ForeignKey(User)
e_id=models.ForeignKey(Event)
u_allowed=models.BooleanField(default=False,verbose_name="User allowed")
rq_date=models.DateField(auto_now_add=True,verbose_name="Request Date")
request_list = (('V', 'Voter'), ('C','Candidate' ))
rq_type=models.CharField(max_length=1,choices=request_list,verbose_name="Request Type")
class Meta:
unique_together=("e_id","u_id","rq_date","rq_type")
I want data given by following sql query
select SecureVirtualElection_answer.u_id_id,SecureVirtualElection_answer.e_id_id from SecureVirtualElection_answer,SecureVirtualElection_userrequestpool
where SecureVirtualElection_userrequestpool.u_id_id=SecureVirtualElection_answer.u_id_id
and SecureVirtualElection_userrequestpool.e_id_id=SecureVirtualElection_answer.e_id_id
and rq_type='C' AND u_allowed=0;
I had been trying to write same in django admin
class AnswerListFilter(SimpleListFilter):
title = _('approved')
parameter_name = 'select'
def lookups(self, request, model_admin):
return (
('yes', _('yes')),
('no', _('no')),
)
def queryset(self, request, q):
print "hello"
if self.value() == 'yes':
print "yes"
return Answer.objects.raw('select SecureVirtualElection_answer.u_id_id,SecureVirtualElection_answer.e_id_id from SecureVirtualElection_answer,SecureVirtualElection_userrequestpool where SecureVirtualElection_userrequestpool.u_id_id=SecureVirtualElection_answer.u_id_id and SecureVirtualElection_userrequestpool.e_id_id=SecureVirtualElection_answer.e_id_id and rq_type=\'C\' AND u_allowed=1')
if self.value() == 'no':
return Answer.objects.raw('select SecureVirtualElection_answer.u_id_id,SecureVirtualElection_answer.e_id_id from SecureVirtualElection_answer,SecureVirtualElection_userrequestpool where SecureVirtualElection_userrequestpool.u_id_id=SecureVirtualElection_answer.u_id_id and SecureVirtualElection_userrequestpool.e_id_id=SecureVirtualElection_answer.e_id_id and rq_type=\'C\' AND u_allowed=0')
print "control should not reach here"
result = UserRequestPool.objects.filter(rq_type='C',u_allowed=False)