【问题标题】:Django Admin Show Image from ImagefieldDjango Admin 显示来自 Imagefield 的图像
【发布时间】:2013-04-24 19:27:54
【问题描述】:

虽然我可以在 list_display 中显示上传的图像,但是否可以在每个模型页面上执行此操作(如在更改模型的页面中)?

一个快速的示例模型是:

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

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

谢谢!

【问题讨论】:

    标签: django django-admin


    【解决方案1】:

    当然。在您的模型类中添加如下方法:

    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
    

    并在您的admin.py 中添加:

    fields = ( 'image_tag', )
    readonly_fields = ('image_tag',)
    

    到您的ModelAdmin。如果您想限制编辑图像字段的能力,请务必将其添加到exclude 属性中。

    注意:对于 Django 1.8 和仅在 readonly_fields 中的“image_tag”,它不会显示。仅在字段中使用“image_tag”,它给出了未知字段的错误。为了正确显示,您需要在字段和 readonly_fields 中使用它。

    【讨论】:

    • 嘿,正在尝试提到的解决方案,但图像仍然没有出现,我看到的是图像 url...有人可以帮忙吗?
    • @Anuradha,您是否包含了image_tag.allow_tags = True 位?否则,标签将被转义并显示完整的&lt;img ...&gt; 标签。不过,您可能应该在一个单独的问题中提出这个问题。
    • @MichaelC.O'Connor,是的,它现在正在工作。在错误的地方添加了 image_tag.allow_tags = True
    • 对于较新版本的 Django (1.9+),请参阅@palamunder 的答案
    • 在 Django v.1.9 中已弃用,@palamunder 已经回答了这个问题。
    【解决方案2】:

    对于 Django 1.9 要在编辑页面中显示图像而不是文件路径,使用 ImageWidget 是一个不错的方法。

    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)
    

    用法:

    class IndividualBirdAdmin(ImageWidgetAdmin):
        image_fields = ['thumbNail', 'detailImage']
    

    图像将显示在 thumbNaildetailImage 字段中

    【讨论】:

    • 感谢您的回答。一个问题:为什么你会弹出 kwargs 的请求?
    • 啊。我自己想通了。 db_field.formfield() 方法不期望请求 kwarg 并且会抛出。
    • 子类化 AdminFileWidget 在我看来确实是更优雅的解决方案,谢谢。
    • 这一直有效,直到 Django 2.1 打破它。请查看更新代码here
    • 最好只使用formfield_overrides 而不是ImageWidgetAdmin 。效果是一样的
    【解决方案3】:

    除了 Michael C. O'Connor 的回答

    请注意,自从 Django v.1.9(更新 - 测试并一直工作到 Django 3.0)

    image_tag.allow_tags = True
    

    已弃用,you should use format_html(), format_html_join(), or mark_safe() instead

    因此,如果您将上传的文件存储在公共 /directory 文件夹中,您的代码应如下所示:

    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'
    

    在你的 admin.py 中添加:

    fields = ['image_tag']
    readonly_fields = ['image_tag']
    

    【讨论】:

    • 为什么要包含“fields = ('image_tag', )”?从测试中删除所有其他字段在查看带有图像的模型时显示。如果您忽略它,它只会将新字段附加到表单的底部。我也不理解对尾随逗号的痴迷,但我只是对这方面很挑剔。
    • 显然,仅当您指定所需的所有其他字段时,才需要 fields = ('image_tag', )。例如,如果您有一个具有 20 个属性的模型,但您不想将所有属性都可视化,您可以通过这种方式仅指定您想要的属性。对逗号的痴迷只是为了避免在之后添加其他内容时丢失一个。它已经在许多与 Django 相关的教程和书籍中使用过,无论如何,不​​一定需要它。我个人觉得它很有用。
    • 谢谢,我只是想确保某些版本的 Django 或 python 的 fields 变量没有副作用。这更有意义。
    • @Sensei 和 palamunder,实际上在尾随逗号方面存在差异 - 当元组只有一个对象或不被视为元组时,它们 必需的。试试foo = ('a'); bar = ('b',); print foo, bar,你会发现不同。我真的很惊讶 Django 示例可以与非集合对象一起使用,但是 Django 做了很多事情。
    • mark_safe 对我有用。指定来自哪里很重要(从 django.utils.html 导入 mark_safe)
    【解决方案4】:

    使用django-imagekit,您可以像这样添加任何图像:

    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
    

    【讨论】:

    • 一石两鸟。非常感谢!
    • 这只会更改列表显示,不会更改管理员更改表单中图像的显示。
    • 这是最快的方式,我可以在 changeform 和 list view 中显示图片。
    【解决方案5】:

    无需修改模型即可在admin中完成

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

    【讨论】:

    • 这仅用于列表视图,不是实际的模型更改表单
    • 如何将文本添加到此?
    • 谢谢!一些改进和补充: '' 用于自定义图像大小。在 Model1Admin 类上使用 @admin.register(Model1) 注释而不是 admin.site.register(Model1, Model1Admin) 行以获得更简洁的代码
    【解决方案6】:

    虽然这里已经分享了一些很好的功能性解决方案,但我觉得非表单标记,例如辅助图像标签,属于模板,而不是附加到 Django 表单小部件或在模型管理类中生成。更语义化的解决方案是:

    管理模板覆盖

    注意:显然我的声誉不够高,无法发布两个以上的简单链接,因此我在以下文本中创建了注释,并在此答案的底部包含了相应的 URL。

    来自Django Admin Site documentation

    覆盖管理模块用来生成管理站点各个页面的许多模板相对容易。您甚至可以为特定应用或特定模型覆盖其中一些模板。

    Django 的django.contrib.admin.options.ModelAdmin(通常在命名空间django.contrib.admin.ModelAdmin 下访问)提供了一系列可能的Django's template loader 模板路径,从最具体到不太具体。这个sn-p是直接从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)
    

    因此,考虑到前面提到的 Django 管理模板覆盖文档和模板搜索路径,假设有人创建了一个应用程序“文章”,其中定义了一个模型类“文章”。如果想要覆盖或扩展模型articles.models.Article 的默认Django 管理站点更改表单,可以执行以下步骤:

    1. 为覆盖文件创建一个模板目录结构。
      • 虽然文档没有提及,但如果 APP_DIRS1 设置为 True,模板加载器将首先查看应用目录。
      • 因为想要通过应用标签和模型覆盖 Django 管理站点模板,所以生成的目录层次结构将是:&lt;project_root&gt;/articles/templates/admin/articles/article/
    2. 在新的目录结构中创建模板文件。
      • 只有管理员更改表单需要被覆盖,所以创建change_form.html
      • 最终的绝对路径为&lt;project_root&gt;/articles/templates/admin/articles/article/change_form.html
    3. 完全覆盖或只是扩展默认的管理员更改表单模板。
      • 我无法在 Django 文档中找到有关默认管理站点模板可用的上下文数据的任何信息,因此我不得不查看 Django 源代码。
        • 默认更改表单模板:github.com/django/django/blob/master/django/contrib/admin/templates/admin/change_form.html
        • 一些相关的上下文字典定义可以在 django.contrib.admin.options.ModelAdmin._changeform_viewdjango.contrib.admin.options.ModelAdmin.render_change_form

    我的解决方案

    假设我在模型上的 ImageField 属性名称是“文件”,我实现图像预览的模板覆盖将类似于:

    {% 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 似乎是生成 ModelForm 的模型实例。顺便说一句,我通常不使用内联 CSS,但不值得为单个规则单独文件。

    来源:

    1. docs.djangoproject.com/en/dev/ref/settings/#app-dirs

    【讨论】:

    • 这篇文章有效。我几乎完全复制了您的代码块,除了我将“src”行更改为:src="{{ original.file.url }}",注意删除斜杠。
    【解决方案7】:

    Venkat Kotra 的 answer 的 Django 2.1 更新。答案在 Django 2.0.7 及更低版本上运行良好。但给出服务器 500 错误(如果 DEBUG=False)或给出

    render() got an unexpected keyword argument 'renderer'

    原因是在 Django 2.1 中:Support for Widget.render() methods without the renderer argument is removed. 所以,参数 renderer 现在是强制性的。我们必须更新AdminImageWidget 的函数render() 以包含参数renderer。它必须在attrs 之后(如果有的话,在kwargs 之前):

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

    【讨论】:

    • 有没有办法在图像中添加悬停 css 类,以便列表显示中的缩略图变大?
    • @Martins 我只发布了一个修复 Django 2.1+ 崩溃的修复程序。而且我不知道悬停css,无能为力。由于您的问题实际上是另一个问题,因此您最好将其发布为新问题。这个线程已经老了,可能没有进一步的讨论(除了像这样的错误修复)。
    【解决方案8】:

    This is 在不修改 models.py 的情况下如何在 django 2.1 上工作:

    在您的 Hero 模型中,您有一个图像字段。:

    headshot = models.ImageField(null=True, blank=True, upload_to="hero_headshots/")
    

    你可以这样做:

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

    【讨论】:

      【解决方案9】:

      @palamunder 的回答在 Django 2.2 上对我有用,但做了一些小改动。

      模型.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"],
      )
      
      

      【讨论】:

        【解决方案10】:

        Django 版本。 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', )
        

        【讨论】:

          【解决方案11】:

          如果您需要在保存前显示图像预览,您可以使用自定义 django 模板 + 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>
          

          【讨论】:

            【解决方案12】:

            我想自己解决这个问题,这就是我想出的

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

            【讨论】:

              【解决方案13】:

              在 Django v3.2 上测试。*

              • 只要你可以在你的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))
              
              • 然后将其添加到admin.py
              list_display = ['image_tag']
              

              【讨论】: