Prevent django admin from escaping html - django

I'm trying to display image thumbnails in django admin's list_display and I am doing it like this:
from django.utils.safestring import mark_safe
class PhotoAdmin(admin.ModelAdmin):
fields = ('title', 'image',)
list_display = ('title', '_get_thumbnail',)
def _get_thumbnail(self, obj):
return mark_safe(u'<img src="%s" />' % obj.admin_thumbnail.url)
Admin keeps displaying the thumbnail as escaped html, although I marked the string as safe. What am I doing wrong?

As of Django 1.9, you can use format_html(), format_html_join(), or allow_tags in your method. See the list_display docs for more info.
The code in the question using mark_safe will work. However a better option for methods like these might be format_html, which will escape arguments.
def _get_thumbnail(self, obj):
return format_html(u'<img src="{}" />', obj.admin_thumbnail.url)
In earlier versions of Django, using mark_safe() would not work, and Django would escape the output. The solution was to give the method an allow_tags attribute with the value set to True.
class PhotoAdmin(admin.ModelAdmin):
fields = ('title', 'image',)
list_display = ('title', '_get_thumbnail',)
def _get_thumbnail(self, obj):
return u'<img src="%s" />' % obj.admin_thumbnail.url
_get_thumbnail.allow_tags = True

I know this is a rather late answer, but I thought a more complete implementation would be helpful to others...
If you don't have it already with django-filer, get easy_thumbnails pip install easy-thumbnails.
# -*- coding: utf-8 -*-
from django.contrib import admin
from easy_thumbnails.files import get_thumbnailer
from models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_display = ('_thumbnail', 'title', )
list_display_links = ('_thumbnail', 'title', ) # This makes the icon clickable too
readonly_fields = ('_thumbnail', )
fields = ('title', 'photo', )
def _thumbnail(self, obj):
if obj.photo:
thumbnailer = get_thumbnailer(obj.photo)
thumb = thumbnailer.get_thumbnail({
'crop': True,
'size': (50, 50),
# Sharpen it up a little, since its so small...
'detail': True,
# Put other options here...
})
# Note: we get the actual width/height rather than
# hard-coding 50, 50, just to be DRYer
return u'<img src="%s" alt="thumbnail: %s" width="%d" height="%d"/>' % (thumb.url, obj.photo.name, thumb.width, thumb.height)
else:
return "[No Image]"
# Optional, Provide a nicer label in the display
_thumbnail.short_description = 'Thumbnail'
# Required, leaves the markup un-escaped
_thumbnail.allow_tags = True
admin.site.register(Photo, PhotoAdmin)

Related

How to display base64 image in django admin?

I have legacy software that stores the institutions logo through django's BinaryField, I need to show this image on the django admin record editing page
this my admin:
class InstituicaoAdmin(admin.ModelAdmin):
list_display = ['ds_instituicao', 'sigla']
search_fields = ['ds_instituicao']
formfield_overrides = {
BinaryField: {'widget': BinaryFileInput()},
}
readonly_fields = ["imagem_logo",]
def imagem_logo(self, obj):
base64Encoded = base64.b64encode(obj.logo)
return mark_safe('<img src="data:;base64,base64Encoded">')
This not work
I want some like this: Image_FIeld
I would use format_html instead of mark_safe:
def imagem_logo(self, obj):
base64Encoded = base64.b64encode(obj.logo)
return format_html('<img src="data:;base64,{}">', base64Encoded)
See the docs at: https://docs.djangoproject.com/en/2.2/ref/utils/#django.utils.html.format_html

Django-admin , show image Thumbnail in Admin Inline ( ChangeForm) [duplicate]

While I can show an uploaded image in list_display is it possible to do this on the per model page (as in the page you get for changing a model)?
A quick sample model would be:
Class Model1(models.Model):
image = models.ImageField(upload_to=directory)
The default admin shows the url of the uploaded image but not the image itself.
Thanks!
Sure. In your model class add a method like:
def image_tag(self):
from django.utils.html import escape
return u'<img src="%s" />' % escape(<URL to the image>)
image_tag.short_description = 'Image'
image_tag.allow_tags = True
and in your admin.py add:
fields = ( 'image_tag', )
readonly_fields = ('image_tag',)
to your ModelAdmin. If you want to restrict the ability to edit the image field, be sure to add it to the exclude attribute.
Note: With Django 1.8 and 'image_tag' only in readonly_fields it did not display. With 'image_tag' only in fields, it gave an error of unknown field. You need it both in fields and in readonly_fields in order to display correctly.
In addition to the answer of Michael C. O'Connor
Note that since Django v.1.9 (updated - tested and worked all the way to Django 3.0)
image_tag.allow_tags = True
is deprecated and you should use format_html(), format_html_join(), or mark_safe() instead
So if you are storing your uploaded files in your public /directory folder, your code should look like this:
from django.utils.html import mark_safe
Class Model1(models.Model):
image = models.ImageField(upload_to=directory)
def image_tag(self):
return mark_safe('<img src="/directory/%s" width="150" height="150" />' % (self.image))
image_tag.short_description = 'Image'
and in your admin.py add:
fields = ['image_tag']
readonly_fields = ['image_tag']
It can be done in admin without modifying model
from django.utils.html import format_html
#admin.register(Model1)
class Model1Admin(admin.ModelAdmin):
def image_tag(self, obj):
return format_html('<img src="{}" />'.format(obj.image.url))
image_tag.short_description = 'Image'
list_display = ['image_tag',]
For Django 1.9
To show image instead of the file path in edit pages, using ImageWidget is nice way to do it.
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.translation import ugettext as _
from django.utils.safestring import mark_safe
from django.contrib import admin
class AdminImageWidget(AdminFileWidget):
def render(self, name, value, attrs=None):
output = []
if value and getattr(value, "url", None):
image_url = value.url
file_name = str(value)
output.append(u' <img src="%s" alt="%s" /> %s ' % \
(image_url, image_url, file_name, _('Change:')))
output.append(super(AdminFileWidget, self).render(name, value, attrs))
return mark_safe(u''.join(output))
class ImageWidgetAdmin(admin.ModelAdmin):
image_fields = []
def formfield_for_dbfield(self, db_field, **kwargs):
if db_field.name in self.image_fields:
request = kwargs.pop("request", None)
kwargs['widget'] = AdminImageWidget
return db_field.formfield(**kwargs)
return super(ImageWidgetAdmin, self).formfield_for_dbfield(db_field, **kwargs)
Usage:
class IndividualBirdAdmin(ImageWidgetAdmin):
image_fields = ['thumbNail', 'detailImage']
Images will show up for the fields, thumbNail and detailImage
With django-imagekit you can add any image like this:
from imagekit.admin import AdminThumbnail
#register(Fancy)
class FancyAdmin(ModelAdmin):
list_display = ['name', 'image_display']
image_display = AdminThumbnail(image_field='image')
image_display.short_description = 'Image'
readonly_fields = ['image_display'] # this is for the change form
While there are some good, functional solutions already shared here, I feel that non-form markup, such as auxiliary image tags, belong in templates, not tacked on to Django form widgets or generated in model admin classes. A more semantic solution is:
Admin Template Overrides
Note: Apparently my reputation isn't high enough to post more than two simple links, so I have created annotations in the following text and included the respective URLs at the bottom of this answer.
From the Django Admin Site documentation:
It is relatively easy to override many of the templates which the admin module uses to generate the various pages of an admin site. You can even override a few of these templates for a specific app, or a specific model.
Django's django.contrib.admin.options.ModelAdmin (commonly accessed under the namespace django.contrib.admin.ModelAdmin) presents a series of possible template paths to Django's template loader in order from most specific to less so. This snippet was copied directly from django.contrib.admin.options.ModelAdmin.render_change_form:
return TemplateResponse(request, form_template or [
"admin/%s/%s/change_form.html" % (app_label, opts.model_name),
"admin/%s/change_form.html" % app_label,
"admin/change_form.html"
], context)
Therefore, considering the aforementioned Django admin template override documentation and the template search paths, suppose one has created an app "articles" in which is defined a model class "Article". If one wants to override or extend only the default Django admin site change form for model articles.models.Article, one would execute the following steps:
Create a template directory structure for the override file.
Although the documentation does not mention it, the template loader will look in app directories first if APP_DIRS1 is set to True.
Because one wants to override the Django admin site template by app label and by model, the resulting directory hierarchy would be: <project_root>/articles/templates/admin/articles/article/
Create the template file(s) in one's new directory structure.
Only the admin change form needs to be overridden so create change_form.html.
The final, absolute path will be <project_root>/articles/templates/admin/articles/article/change_form.html
Completely override or simply extend the default admin change form template.
I wasn't able to locate any information in the Django documentation concerning the context data available to the default admin site templates so I was forced to look at the Django source code.
Default change form template: github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
A few of the relevant context dictionary definitions can be found in
django.contrib.admin.options.ModelAdmin._changeform_view and django.contrib.admin.options.ModelAdmin.render_change_form
My Solution
Assuming that my ImageField attribute name on the model is "file", my template override to implement image previews would be similar to this:
{% extends "admin/change_form.html" %}
{% block field_sets %}
{% if original %}
<div class="aligned form-row">
<div>
<label>Preview:</label>
<img
alt="image preview"
src="/{{ original.file.url }}"
style="max-height: 300px;">
</div>
</div>
{% endif %}
{% for fieldset in adminform %}
{% include "admin/includes/fieldset.html" %}
{% endfor %}
{% endblock %}
original appears to be the model instance from which the ModelForm was generated. As an aside, I usually don't use inline CSS but it wasn't worth a separate file for a single rule.
Sources:
docs.djangoproject.com/en/dev/ref/settings/#app-dirs
I was trying to figure it out myself and this is what i came up with
#admin.register(ToDo)
class ToDoAdmin(admin.ModelAdmin):
def image_tag(self, obj):
return format_html('<img src="{}" width="auto" height="200px" />'.format(obj.img.url))
image_tag.short_description = 'Image'
list_display = ['image_tag']
readonly_fields = ['image_tag']
This is how it worked for django 2.1 without modifying models.py:
In your Hero model, you have an image field.:
headshot = models.ImageField(null=True, blank=True, upload_to="hero_headshots/")
You can do it like this:
#admin.register(Hero)
class HeroAdmin(admin.ModelAdmin, ExportCsvMixin):
readonly_fields = [..., "headshot_image"]
def headshot_image(self, obj):
return mark_safe('<img src="{url}" width="{width}" height={height} />'.format(
url = obj.headshot.url,
width=obj.headshot.width,
height=obj.headshot.height,
)
)
Django 2.1 update for Venkat Kotra's answer. The answer works fine on Django 2.0.7 and below. But gives server 500 error (if DEBUG=False) or gives
render() got an unexpected keyword argument 'renderer'
The reason is that in Django 2.1: Support for Widget.render() methods without the renderer argument is removed. So, param renderer is mandatory now. We must update function render() of AdminImageWidget to include param renderer. And it must be after attrs (before kwargs if you have it):
class AdminImageWidget(AdminFileWidget):
def render(self, name, value, attrs=None, renderer=None):
output = []
if value and getattr(value, "url", None):
image_url = value.url
file_name = str(value)
output.append(u' <img src="%s" alt="%s" /> %s ' % \
(image_url, image_url, file_name, _('Change:')))
output.append(super(AdminFileWidget, self).render(name, value, attrs, renderer))
return mark_safe(u''.join(output))
Django ver. 3.0.3
models.py:
def image_tag(self):
from django.utils.html import mark_safe
return mark_safe('<img src="%s" width="100px" height="100px" />'%(self.image.url))
image_tag.short_description = 'Image'
admin.py:
list_display = ('image_tag', )
Tested on Django v3.2.*
Just you can this code in your model.py
from django.db import models
from django.utils.html import mark_safe
class Book(models.Model):
image = models.ImageField()
def image_tag(self):
if self.image != '':
return mark_safe('<img src="%s%s" width="150" height="150" />' % (f'{settings.MEDIA_URL}', self.image))
Then add this in admin.py
list_display = ['image_tag']
#palamunder's answer worked for me on Django 2.2 with a couple minor changes.
Model.py
from django.utils.safestring import mark_safe
class AdminCategory(models.Model):
image = models.ImageField(_("Image"),
upload_to='categories/',
blank=True,
default='placeholder.png')
def image_tag(self):
return mark_safe('<img src="%s" width="150" height="150" />' % (
self.image.url)) # Get Image url
image_tag.short_description = 'Image'
Admin.py
admin.site.register(
AdminCategory,
list_display=["image_tag"],
)
If you need to show image preview before save, you could use custom django template + js
admin.py
class UploadedImagePreview(object):
short_description = _('Thumbnail')
allow_tags = True
def __init__(self, image_field, template, short_description=None, width=None, height=None):
self.image_field = image_field
self.template = template
if short_description:
self.short_description = short_description
self.width = width or 200
self.height = height or 200
def __call__(self, obj):
try:
image = getattr(obj, self.image_field)
except AttributeError:
raise Exception('The property %s is not defined on %s.' %
(self.image_field, obj.__class__.__name__))
template = self.template
return render_to_string(template, {
'width': self.width,
'height': self.height,
'watch_field_id': 'id_' + self.image_field # id_<field_name> is default ID
# for ImageField input named `<field_name>` (in Django Admin)
})
#admin.register(MyModel)
class MainPageBannerAdmin(ModelAdmin):
image_preview = UploadedImagePreview(image_field='image', template='admin/image_preview.html',
short_description='uploaded image', width=245, height=245)
readonly_fields = ('image_preview',)
fields = (('image', 'image_preview'), 'title')
image_preview.html
<img id="preview_{{ watch_field_id }}" style="display: none; width: {{ width }}px; height: {{ height }}px" alt="">
<script>
function handleFileSelect(event) {
var files = event.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails
for (var i = 0, f; f = files[i]; i++) {
// Only process image files
if (!f.type.match('image.*')) continue;
// Init FileReader()
// See: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
var reader = new FileReader();
// Closure to capture the file information
reader.onload = (function () {
return function (e) {
// Render background image
document.getElementById('preview_{{watch_field_id}}').src = e.target.result;
// Set `display: block` to preview image container
document.getElementById('preview_{{watch_field_id}}').style.display = 'block';
};
})(f);
// Read in the image file as a data URL
reader.readAsDataURL(f);
}
}
// Change img src after change file input
// watch_field_id — is ID for ImageField input
document.getElementById('{{ watch_field_id }}').addEventListener('change', handleFileSelect, false);
</script>
For example, there is Product model below:
# "models.py"
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=50)
price = models.DecimalField(decimal_places=2, max_digits=5)
image = models.ImageField()
def __str__(self):
return self.name
And, there is Product admin below:
# "admin.py"
from django.contrib import admin
from .models import Product
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
pass
Then, an uploaded image is not displayed in "Change" page in Django Admin as shown below:
Now, I override AdminFileWidget then assign CustomAdminFileWidget to formfield_overrides as shown below:
# "admin.py"
from django.contrib import admin
from .models import Product
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.html import format_html
from django.db import models
# Here
class CustomAdminFileWidget(AdminFileWidget):
def render(self, name, value, attrs=None, renderer=None):
result = []
if hasattr(value, "url"):
result.append(
f'''<a href="{value.url}" target="_blank">
<img
src="{value.url}" alt="{value}"
width="100" height="100"
style="object-fit: cover;"
/>
</a>'''
)
result.append(super().render(name, value, attrs, renderer))
return format_html("".join(result))
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
formfield_overrides = { # Here
models.ImageField: {"widget": CustomAdminFileWidget}
}
Then, an uploaded image is displayed in "Change" page in Django Admin as shown below:
You can also see my answer explaining how to display uploaded images in "Change List" page in Django Admin.

add link to change page using raw_id_fields in django admin

Is there any chance to add link to change page of a Foreign key object next to 'raw_id_fields' in django admin?
I use raw_id_fields to reduce number of queries to mysql.
I could edit or add a foreign key object when I didn't use raw_id_fields.
How to do it with raw_id_fields?
#AntoinePinsard, I've tried the second solution. The field 'Edit My FK' was created but there is nothing except for a hyphen.
That's my modeladmin.
class FlatAdmin(admin.ModelAdmin):
inlines = [NeedInline]
readonly_fields = ('flat_house_edit_link',)
raw_id_fields = ('flat_house',)
list_display = ('show_date','show_block','show_house','show_rooms','show_price','show_stage')
list_filter = ('flat_house__house_block','flat_rooms')
search_fields = ('flat_house__id',)
field = ['flat_house','flat_house_edit_link','flat_owner','flat_price','flat_rooms','flat_total_sq','flat_life_sq','flat_kitchen_sq','flat_floors']
def flat_house_edit_link(self, instance):
if instance:
fk_id = instance.user_id
else:
fk_id = None
if fk_id:
opts = instance._meta.get_field('flat_house').rel.model._meta
related_url = reverse(
'admin:{}_{}_change/?_to_field=id&_popup=1'.format(
opts.ha,
opts.house,
),
args=[fk_id],
)
return format_html(
'<a target=_blank href="{}">Go!</a>', related_url)
else:
return "No related object"
flat_house_edit_link.short_description = "Change house"
admin.site.register(Flat,FlatAdmin)
Note This behavior is now built-in since Django 1.10.
You can create a custom widget to render this field as you would like to.
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
from django.core.urlresolvers import reverse, NoReverseMatch
from django.utils.html import format_html
class ForeignKeyLinkedRawIdWidget(ForeignKeyRawIdWidget):
def render(self, name, value, attrs=None):
output = super().render(name, value, attrs)
try:
related_url = reverse(
'admin:{}_{}_change'.format(
self.rel.model._meta.app_label,
self.rel.model._meta.model_name,
),
args=[value],
)
except NoReverseMatch:
return output
return format_html('{output} edit',
output=output, url=related_url)
And use this widget in your form, rather than using raw_id_fields:
from myapp.forms.widgets import ForeignKeyLinkedRawIdWidget
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = '__all__'
widgets = {
'my_foreignkey': ForeignKeyLinkedRawIdWidget(),
}
class MyModelAdmin(admin.ModelAdmin):
form = MyForm
However, it would be much simpler to add it as a "fake" readonly_field below the actual field:
from django.core.urlresolvers import reverse
from django.utils.html import format_html
class MyModelAdmin(admin.ModelAdmin):
readonly_fields = ['my_foreignkey_link']
raw_id_fields = ['my_foreignkey']
fields = [..., 'my_foreignkey', 'my_foreignkey_link', ...]
def my_foreignkey_link(self, instance):
if instance:
fk_id = instance.my_foreignkey_id
else:
fk_id = None
if fk_id:
opts = instance._meta.get_field('my_foreignkey').rel.model._meta
related_url = reverse(
'admin:{}_{}_change'.format(
opts.app_label,
opts.model_name,
),
args=[fk_id],
)
return format_html(
'<a target=_blank href="{}">Go!</a>', related_url)
else:
return "No related object"
my_foreignkey_link.short_description = "Edit My FK"
Further reading: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields
Here's a snippet page (updated for 6 years) that does it for me:
https://djangosnippets.org/snippets/2217/
Just use ImproveRawIdFieldsForm in place of ModelAdmin and all raw_id fields automatically links the object name displayed next to the id input.

Django - Costum admin filter not by field [duplicate]

How can I add a custom filter to django admin (the filters that appear on the right side of a model dashboard)? I know its easy to include a filter based on a field of that model, but what about a "calculated" field like this:
class NewsItem(models.Model):
headline = models.CharField(max_length=4096, blank=False)
byline_1 = models.CharField(max_length=4096, blank=True)
dateline = models.DateTimeField(help_text=_("date/time that appears on article"))
body_copy = models.TextField(blank=False)
when_to_publish = models.DateTimeField(verbose_name="When to publish", blank=True, null=True)
# HOW CAN I HAVE "is_live" as part of the admin filter? It's a calculated state!!
def is_live(self):
if self.when_to_publish is not None:
if ( self.when_to_publish < datetime.now() ):
return """ <img alt="True" src="/media/img/admin/icon-yes.gif"/> """
else:
return """ <img alt="False" src="/media/img/admin/icon-no.gif"/> """
is_live.allow_tags = True
class NewsItemAdmin(admin.ModelAdmin):
form = NewsItemAdminForm
list_display = ('headline', 'id', 'is_live')
list_filter = ('is_live') # how can i make this work??
Thanks to gpilotino for giving me the push into the right direction for implementing this.
I noticed the question's code is using a datetime to figure out when its live . So I used the DateFieldFilterSpec and subclassed it.
from django.db import models
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec,DateFieldFilterSpec
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext as _
from datetime import datetime
class IsLiveFilterSpec(DateFieldFilterSpec):
"""
Adds filtering by future and previous values in the admin
filter sidebar. Set the is_live_filter filter in the model field attribute
'is_live_filter'. my_model_field.is_live_filter = True
"""
def __init__(self, f, request, params, model, model_admin):
super(IsLiveFilterSpec, self).__init__(f, request, params, model,
model_admin)
today = datetime.now()
self.links = (
(_('Any'), {}),
(_('Yes'), {'%s__lte' % self.field.name: str(today),
}),
(_('No'), {'%s__gte' % self.field.name: str(today),
}),
)
def title(self):
return "Is Live"
# registering the filter
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_live_filter', False),
IsLiveFilterSpec))
To use you can put the above code into a filters.py, and import it in the model you want to add the filter to
you have to write a custom FilterSpec (not documentend anywhere).
Look here for an example:
http://www.djangosnippets.org/snippets/1051/
In current django development version there is the support for custom filters: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter
You can't, unfortunately. Currently non-field items can not be used as list_filter entries.
Note that your admin class wouldn't have worked even if it was a field, as a single-item tuple needs a comma: ('is_live',)
Just a sidenote: You can use the deafult ticks on Django admin more easily like this:
def is_live(self):
if self.when_to_publish is not None:
if ( self.when_to_publish < datetime.now() ):
return True
else:
return False
is_live.boolean = True
Not an optimal way (CPU-wise) but simple and will work, so I do it this way (for my small database). My Django version is 1.6.
In admin.py:
class IsLiveFilter(admin.SimpleListFilter):
title = 'Live'
parameter_name = 'islive'
def lookups(self, request, model_admin):
return (
('1', 'islive'),
)
def queryset(self, request, queryset):
if self.value():
array = []
for element in queryset:
if element.is_live.__call__() == True:
q_array.append(element.id)
return queryset.filter(pk__in=q_array)
...
class NewsItemAdmin(admin.ModelAdmin):
form = NewsItemAdminForm
list_display = ('headline', 'id', 'is_live')
list_filter = (IsLiveFilter)
Key idea here is to access custom fields in a QuerySet via __call__() function.
The user supplies goods to some countries postage free. I wanted to filter those countries:
All - all countries, Yes - postage free, No - charged postage.
The main answer for this question did not work for me (Django 1.3) I think because there was no field_path parameter provided in the __init__ method. Also it subclassed DateFieldFilterSpec. The postage field is a FloatField
from django.contrib.admin.filterspecs import FilterSpec
class IsFreePostage(FilterSpec):
def __init__(self, f, request, params, model, model_admin, field_path=None):
super(IsFreePostage, self).__init__(f, request, params, model,
model_admin, field_path)
self.removes = {
'Yes': ['postage__gt'],
'No': ['postage__exact'],
'All': ['postage__exact', 'postage__gt'] }
self.links = (
('All', {}),
('Yes', {'postage__exact': 0}),
('No', {'postage__gt': 0}))
if request.GET.has_key('postage__exact'):
self.ttl = 'Yes'
elif request.GET.has_key('postage__gt'):
self.ttl = 'No'
else:
self.ttl = 'All'
def choices(self, cl):
for title, param_dict in self.links:
yield {'selected': title == self.ttl,
'query_string': cl.get_query_string(param_dict,
self.removes[title]),
'display': title}
def title(self):
return 'Free Postage'
FilterSpec.filter_specs.insert(0,
(lambda f: getattr(f, 'free_postage', False), IsFreePostage))
In self.links we supply dicts. used to construct HTTP query strings like ?postage__exact=0 for each of the possible filters. Filters I think are cumulative so if there was a previous request for 'No' and now we have a request for 'Yes' we have to remove the
'No' query. self.removes specifies what needs to be removed for each query. The choices method constructs the query strings, says which filter has been selected and sets the displayed name of the filter.
Here is the answer and implemented the custom filter as simple as possible this might help
Django admin date range filter

Custom Filter for Date Field in Django Admin, Django 1.2

Is this still valid syntax for Django 1.2?
Custom Filter in Django Admin on Django 1.3 or below
I have tried it, but the list_filter option in the admin class is not recognizing my custom filter.
How should the custom filter be added to the list_filter so that it displays?
class MyModelAdmin(admin.ModelAdmin):
...
list_filter = ['is_expired_filter']
Here my 'is_expired_filter' is my newly registered custom filter, which is what the Author says he does like so:
list_filter = ('is_live')
But this is not recognized by Django, and the error I get when I load the admin page is
Exception Type: ImproperlyConfigured
Exception Value: 'PositionAdmin.list_filter[2]' refers to field 'is_expired_filter' that is missing from model 'Position'
Perhaps my mistake is that I am not sure how the original code is used by the Author of that question once he/she implements a custom filter.
Here is the original code:
def is_live(self):
if self.when_to_publish is not None:
if ( self.when_to_publish < datetime.now() ):
return """ <img alt="True" src="/media/img/admin/icon-yes.gif"/> """
else:
return """ <img alt="False" src="/media/img/admin/icon-no.gif"/> """
is_live.allow_tags = True
Now that I have a handle on what I think you want, I'm assuming you have a model that you want to filter by a DateField like:
class Position(models.Model):
expiration_date = models.DateField()
...
which you should now modify to
class Position(models.Model):
expiration_date = models.DateField()
expiration_date.is_expired_filter = True
...
What you want to do is add to your admin.py a new filter class
from django.contrib.admin.filterspecs import FilterSpec, DateFieldFilterSpec
from django.utils.translation import ugettext as _
from datetime import datetime, date
class ExpiredFilterSpec(DateFieldFilterSpec):
"""
Adds filtering by future and previous values in the admin
filter sidebar. Set the is_expired_filter filter in the model field
attribute 'is_expired_filter'.
my_model_field.is_expired_filter = True
"""
def __init__(self, f, request, params, model, model_admin, **kwargs):
super(ExpiredFilterSpec, self).__init__(f, request, params, model,
model_admin, **kwargs)
today = date.today()
self.links = (
(_('All'), {}),
(_('Not Expired'), {'%s__lt' % self.field.name: str(today),
}),
(_('Expired'), {'%s__gte' % self.field.name: str(today),
}))
def title(self):
return "Filter By Expiration Date"
# registering the filter
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_expired_filter', False),
ExpiredFilterSpec))
class PositionAdmin(admin.ModelAdmin):
list_filter = ['expiration_date']
Almost copying your link Custom Filter in Django Admin on Django 1.3 or below word for word, I came up with this.
from django.contrib.admin.filterspecs import FilterSpec, ChoicesFilterSpec, DateFieldFilterSpec
from django.utils.encoding import smart_unicode
from django.utils.translation import ugettext as _
from datetime import datetime
class IsExpiredFilterSpec(DateFieldFilterSpec):
"""
Adds filtering by future and previous values in the admin
filter sidebar. Set the is_expired_filter filter in the model field
attribute 'is_expired_filter'.
my_model_field.is_expired_filter = True
"""
def __init__(self, f, request, params, model, model_admin):
super(IsExpiredFilterSpec, self).__init__(f, request, params, model,
model_admin)
# -- You'll need to edit this to make it do what you want. --
# today = datetime.now()
# self.links = (
# (_('Any'), {}),
# (_('Yes'), {'%s__lte' % self.field.name: str(today),
# }),
# (_('No'), {'%s__gte' % self.field.name: str(today),
# }),
#
# )
def title(self):
return "Is Expired"
\# registering the filter
FilterSpec.filter_specs.insert(0, (lambda f: getattr(f, 'is_expired_filter', False),
IsExpiredFilterSpec))
class MyModelAdmin(admin.ModelAdmin):
...
MODEL_FIELD_TO_FILTER.is_expired_filter = True
list_filters = ['MODEL_FIELD_TO_FILTER']
UPDATE: Made a change thanks to jimbob. MODEL_FIELD_TO_FILTER would be the field you want to filter.