Django Admin Show Image from Imagefield
Asked Answered
T

14

97

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!

Teem answered 30/4, 2013 at 19:13 Comment(0)
I
111

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']
Irrationality answered 22/6, 2016 at 10:16 Comment(6)
Why include "fields = ( 'image_tag', )"? From testing that removes all other fields from being shown when viewing the model with the image. If you leave that out it just appends the new field to the bottom of the form. I also don't understand the obsession with trailing commas but I'm just being nit picky about that aspect.Knudson
Apparently, fields = ('image_tag', ) is needed only if you specify all the other fields that you want. For example, if you have a model with 20 attributes, but you don't want to visualize all of them, you can specify in such way only those that you want to. The obsession with the commas is just to avoid missing one if you add something else after. It has been used in many tutorials and books related to Django, in any case, it is not necessarily needed. I, personally, find it useful.Irrationality
Thanks, I just wanted to make sure there wasn't some side effect with the fields variable for certain version of Django or python. That makes more sense.Knudson
@Knudson and palamunder, actually there is a difference regarding the trailing commas - they are required when a tuple has only a single object, or it's not treated as a tuple. Try foo = ('a'); bar = ('b',); print foo, bar, and you'll see the difference. I'm actually surprised that Django example works with a non-collection object, but then Django does a lot of things well.Dispassion
mark_safe works for me. It's important to specify where come from (from django.utils.html import mark_safe)Dhruv
I doesn't work at all.Avery
V
139

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.

Vicarious answered 30/4, 2013 at 19:29 Comment(5)
heyy, was trying out the solution metioned but the image still doesn't appear, what I can see is the image url... Can somebody help?Removed
@Anuradha, did you include the image_tag.allow_tags = True bit? Otherwise, the tags will be escaped and the full <img ...> tag will be displayed. You should probably ask this in a separate question though.Corycorybant
@MichaelC.O'Connor, yes its working now. Had added image_tag.allow_tags = True at wrong placeRemoved
For newer versions of Django (1.9+) see the answer by @palamunderAnkylosis
deprecated in Django v.1.9, @palamunder has answered this.Redistrict
I
111

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']
Irrationality answered 22/6, 2016 at 10:16 Comment(6)
Why include "fields = ( 'image_tag', )"? From testing that removes all other fields from being shown when viewing the model with the image. If you leave that out it just appends the new field to the bottom of the form. I also don't understand the obsession with trailing commas but I'm just being nit picky about that aspect.Knudson
Apparently, fields = ('image_tag', ) is needed only if you specify all the other fields that you want. For example, if you have a model with 20 attributes, but you don't want to visualize all of them, you can specify in such way only those that you want to. The obsession with the commas is just to avoid missing one if you add something else after. It has been used in many tutorials and books related to Django, in any case, it is not necessarily needed. I, personally, find it useful.Irrationality
Thanks, I just wanted to make sure there wasn't some side effect with the fields variable for certain version of Django or python. That makes more sense.Knudson
@Knudson and palamunder, actually there is a difference regarding the trailing commas - they are required when a tuple has only a single object, or it's not treated as a tuple. Try foo = ('a'); bar = ('b',); print foo, bar, and you'll see the difference. I'm actually surprised that Django example works with a non-collection object, but then Django does a lot of things well.Dispassion
mark_safe works for me. It's important to specify where come from (from django.utils.html import mark_safe)Dhruv
I doesn't work at all.Avery
U
69

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',]
Ustkamenogorsk answered 21/11, 2016 at 8:31 Comment(4)
This is only for the list view, not the actual model change formNyberg
How would help text be added onto this?Riflery
Thanks! a couple of improvements and complementations: '<img src="{}"width="150" height="150" />' for custom image size. Use @admin.register(Model1) annotation over Model1Admin class instead of admin.site.register(Model1, Model1Admin) line for a little cleaner codeFlatwise
You're kinda missing the point of format_html by doing the actual formatting yourself. Those arguments passed to str.format should be passed instead to the format_html functionSearch
C
20

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

Coriecorilla answered 25/9, 2015 at 22:10 Comment(5)
Thanks for this answer. One question: Why do you pop request from kwargs?Kcal
Ah. Figured it out myself. db_field.formfield() method does not expect a request kwarg and will throw.Kcal
Subclassing AdminFileWidget does seem to me as the more elegant solution, thanks.Nellnella
This works until Django 2.1 break it. Please see updated code hereLoris
Even better to just simply use formfield_overrides instead of ImageWidgetAdmin . It's the same effectivelyViridescent
C
17

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
Combs answered 2/8, 2016 at 10:32 Comment(3)
Two birds with one stone. Thanks a lot!Ancel
This only changes the list display, and not the display of the image in the admin change form.Society
This was the fastest way, I was able to show pics in changeform and list view.Pursuance
H
11

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
Han answered 22/5, 2017 at 21:46 Comment(1)
This post works. I almost copied your code block exactly, except for that I changed the "src" line to: src="{{ original.file.url }}", note the removal of slash.Faulkner
B
8

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']
Buffalo answered 13/7, 2020 at 17:29 Comment(1)
That's not how format_html is supposed to be used. Read the docsSearch
C
5

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,
            )
    )
Caseworm answered 11/11, 2018 at 5:26 Comment(0)
K
4

Tested on Django v3.2.*

  • Just you can write 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']
Kuroshio answered 25/11, 2021 at 21:29 Comment(0)
L
3

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))
Loris answered 21/8, 2018 at 16:18 Comment(2)
is there a way to add on hover css class to the image so the thumbnail in list display gets bigger?Potaufeu
@Potaufeu I only post a fix which fix the crash on Django 2.1+. And I don't know hover css, couldn't help. Since your question is, in fact, another question, you may better post it as new question. This thread is getting old and might not have further discussion (except bug fix like this one).Loris
C
3

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', )
Coletta answered 30/3, 2020 at 20:24 Comment(0)
A
3

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:

enter image description here

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:

enter image description here

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

Avery answered 16/1, 2023 at 13:43 Comment(0)
A
2

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

Appaloosa answered 5/12, 2019 at 22:2 Comment(0)
V
0

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>
Vyse answered 22/6, 2020 at 12:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.