Django custom SimpleListFilter - django

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)

Related

How do I fix 'Message' instance needs to have a primary key value before this relationship can be used

When I try to add a Message from the admin pages I get the 'Message' instance needs to have a primary key value before this relationship can be used.
Here is my models.py Message class...
class Message(models.Model):
""" Message as sent through a Submission. """
default_id = time.time()
#adding primary key to satisfy error
id = models.BigAutoField(primary_key=True, auto_created=True)
title = models.CharField(max_length=200, verbose_name=_('title'))
slug = models.SlugField(verbose_name=_('slug'))
newsletter = models.ForeignKey(
Newsletter, verbose_name=_('newsletter'), on_delete=models.CASCADE, default=get_default_newsletter
)
date_create = models.DateTimeField(
verbose_name=_('created'), auto_now_add=True, editable=False
)
date_modify = models.DateTimeField(
verbose_name=_('modified'), auto_now=True, editable=False
)
class Meta:
verbose_name = _('message')
verbose_name_plural = _('messages')
unique_together = ('slug', 'newsletter')
#order_with_respect_to = 'newsletter'
def __str__(self):
try:
return _("%(title)s in %(newsletter)s") % {
'title': self.title,
'newsletter': self.newsletter
}
except Newsletter.DoesNotExist:
logger.warning('No newsletter has been set for this message yet.')
return self.title
def get_next_article_sortorder(self):
""" Get next available sortorder for Article. """
next_order = self.articles.aggregate(
models.Max('sortorder')
)['sortorder__max']
if next_order:
return next_order + 10
else:
return 10
#cached_property
def _templates(self):
"""Return a (subject_template, text_template, html_template) tuple."""
return self.newsletter.get_templates('message')
#property
def subject_template(self):
return self._templates[0]
#property
def text_template(self):
return self._templates[1]
#property
def html_template(self):
return self._templates[2]
#classmethod
def get_default(cls):
try:
return cls.objects.order_by('-date_create').all()[0]
except IndexError:
return None
and the admin.py module
class MessageAdmin(NewsletterAdminLinkMixin, ExtendibleModelAdminMixin,
admin.ModelAdmin):
logger.critical('MessageAdmin')
save_as = True
list_display = (
'admin_title', 'admin_newsletter', 'admin_preview', 'date_create',
'date_modify'
)
list_filter = ('newsletter', )
date_hierarchy = 'date_create'
prepopulated_fields = {'slug': ('title',)}
inlines = [ArticleInline, AttachmentInline, ]
""" List extensions """
def admin_title(self, obj):
return format_html('{}', obj.id, obj.title)
admin_title.short_description = _('message')
def admin_preview(self, obj):
url = reverse('admin:' + self._view_name('preview'), args=(obj.id,),
current_app=self.admin_site.name)
return format_html('{}', url, _("Preview"))
admin_preview.short_description = ''
""" Views """
def preview(self, request, object_id):
logger.critical('preview object_id = %s', object_id)
return render(
request,
"admin/newsletter/message/preview.html",
{'message': self._getobj(request, object_id),
'attachments': Attachment.objects.filter(message_id=object_id)},
)
#xframe_options_sameorigin
def preview_html(self, request, object_id):
logger.critical('preview_html object_id = %s', object_id)
message = self._getobj(request, object_id)
if not message.html_template:
raise Http404(_(
'No HTML template associated with the newsletter this '
'message belongs to.'
))
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(message.html_template.render(c))
#xframe_options_sameorigin
def preview_text(self, request, object_id):
message = self._getobj(request, object_id)
c = {
'message': message,
'site': Site.objects.get_current(),
'newsletter': message.newsletter,
'date': now(),
'STATIC_URL': settings.STATIC_URL,
'MEDIA_URL': settings.MEDIA_URL
}
return HttpResponse(
message.text_template.render(c),
content_type='text/plain'
)
def submit(self, request, object_id):
submission = Submission.from_message(self._getobj(request, object_id))
change_url = reverse(
'admin:newsletter_submission_change', args=[submission.id])
return HttpResponseRedirect(change_url)
def subscribers_json(self, request, object_id):
message = self._getobj(request, object_id)
json = serializers.serialize(
"json", message.newsletter.get_subscriptions(), fields=()
)
return HttpResponse(json, content_type='application/json')
""" URLs """
def get_urls(self):
urls = super().get_urls()
my_urls = [
path('<object_id>/preview/',
self._wrap(self.preview),
name=self._view_name('preview')),
path('<object_id>/preview/html/',
self._wrap(self.preview_html),
name=self._view_name('preview_html')),
path('<object_id>/preview/text/',
self._wrap(self.preview_text),
name=self._view_name('preview_text')),
path('<object_id>/submit/',
self._wrap(self.submit),
name=self._view_name('submit')),
path('<object_id>/subscribers/json/',
self._wrap(self.subscribers_json),
name=self._view_name('subscribers_json')),
]
return my_urls + urls
I have tried a number of the fixes recommended in posts on this error here in stackoverflow and on github. but none have solved the problem.
I created the id = models.BigAutoField(primary_key=True, auto_created=True) and varified in the sqlite db that the primary key and auto increment are in the structure.
I feel like creating a through or xref table between Message and Article would solve it, but I hesitate because the developers of Jazzband Newsletter (https://github.com/jazzband/django-newsletter) did it this way for a reason and that Django or Python updates have let this creep in.

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

Test a custom django filter

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)

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), )

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)