Django 管理显示图片来自 Imagefield

虽然我可以在 list _ display 中显示上传的图像,但是是否可以在每个模型页面上这样做(就像在用于更改模型的页面中那样) ?

一个简单的示例模型是:

Class Model1(models.Model):
image = models.ImageField(upload_to=directory)

默认管理员显示上传图像的 URL,但不显示图像本身。

谢谢!

96991 次浏览

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.

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

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']

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

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',]

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

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

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

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


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

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>

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']

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']