Django Admin Show Image from Imagefield

Question:

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!

Asked By: Jeff_Hd

||

Answers:

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.

Answered By: Michael C. O'Connor

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' <a href="%s" target="_blank"><img src="%s" alt="%s" /></a> %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

Answered By: Venkat Kotra

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']
Answered By: palamunder

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
Answered By: Risadinha

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',]
Answered By: Serjik

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:

  1. 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/
  2. 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
  3. 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:

  1. docs.djangoproject.com/en/dev/ref/settings/#app-dirs
Answered By: monotonee

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' <a href="%s" target="_blank"><img src="%s" alt="%s" /></a> %s ' % 
                      (image_url, image_url, file_name, _('Change:')))
        output.append(super(AdminFileWidget, self).render(name, value, attrs, renderer))
        return mark_safe(u''.join(output))
Answered By: John Pang

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,
            )
    )
Answered By: Alex Jolig

@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"],
)

Answered By: CyberDemic

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', )
Answered By: Gustavo Mex

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>
Answered By: Arseniy Lebedev

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']
Answered By: Dettlaff

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']
Answered By: Beemen Sameh

You need to 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, you can display an uploaded image as shown below:

enter image description here

You can also see my answer explaining how to display uploaded images in "Change List" page in Django Admin.

Answered By: Kai – Kazuya Ito