10

I'm trying to display inline uploaded images in "Change List" page in Django Admin:

enter image description here

This is my code 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)
    
    def __str__(self):
        return self.name

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField()

    def __str__(self):
        return self.image.url
# "admin.py"

from django.contrib import admin
from .models import Product

class ProductImageInline(admin.TabularInline):
    model = ProductImage

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = (ProductImageInline,)

So, how can I display inline uploaded images in "Change" page in Django Admin?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Shaniqwa
  • 1,924
  • 2
  • 17
  • 29

6 Answers6

11

I managed to do it after a lot of digging. Unfortunately I couldn't find the source I used (too many tabs, too many didn't work..) . So the idea was to create an Image Widget, that appends some html script.

from django.contrib.admin.widgets import AdminFileWidget

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" width="150" height="150"  style="object-fit: cover;"/></a> %s ' % \
                (image_url, image_url, file_name, _('')))
        output.append(super(AdminFileWidget, self).render(name, value, attrs))
        return mark_safe(u''.join(output))


class CollectionImageInline(admin.TabularInline):
    formfield_overrides = {models.ImageField: {'widget': AdminImageWidget}}

the result : (I changed admin theme to default regardless to this issue) enter image description here

radoh
  • 4,554
  • 5
  • 30
  • 45
Shaniqwa
  • 1,924
  • 2
  • 17
  • 29
  • 2
    This worked, thanks. I would add imports on top: `from django.utils.safestring import marksafe from django.db import models` – horbor Nov 16 '17 at 13:39
7

The simple solution that works for me in Django 3.0:

In models.py add:

from django.utils.safestring import mark_safe

def image_preview(self):
    if self.image:
        return mark_safe('<img src="{0}" width="150" height="150" />'.format(self.image.url))
    else:
        return '(No image)'

in admin.py:

class ProductImagesAdmin(admin.TabularInline):
    model = ProductImages
    readonly_fields = ('image_preview',)
Oleg Barbos
  • 178
  • 1
  • 10
5

You'll want to add a method to the model.py:

def thumbnail(self):
  return u'<img src="%s" />' % (self.image.url)

thumbnail.short_description = 'Thumbnail'

Then in admin.py you'll want to add:

fields = ( 'thumbnail', )
readonly_fields = ('thumbail',)

That will add the thumbnail there.

4

Following on, to @shaniqwa answer, django(2.2.7) now requires renderer in render(),

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(
                f' <a href="{image_url}" target="_blank">'
                f'  <img src="{image_url}" alt="{file_name}" width="150" height="150" '
                f'style="object-fit: cover;"/> </a>')

        output.append(super(AdminFileWidget, self).render(name, value, attrs, renderer))
        return mark_safe(u''.join(output))


class ProductImagesAdmin(admin.TabularInline):
    model = ProductImages
    extra = 1
    formfield_overrides = {
        models.ImageField: {'widget': AdminImageWidget}
    }

Ansuman
  • 428
  • 1
  • 4
  • 15
2

Cleaned up version of the first answer https://stackoverflow.com/a/44051507/2020723 - ready of Django >3, python 3.x:

class AdminImageWidget(AdminFileWidget):
    """Admin widget for showing clickable thumbnail of Image file fields"""

    def render(self, name, value, attrs=None, renderer=None):
        html = super().render(name, value, attrs, renderer)
        if value and getattr(value, 'url', None):
            html = format_html('<a href="{0}" target="_blank"><img src="{0}" alt="{1}" width="150" height="150" style="object-fit: contain;"/></a>', value.url, str(value)) + html
        return html
radoh
  • 4,554
  • 5
  • 30
  • 45
  • Thankyou for your answer. But when I click "submit" or "delete" the following error comes up ```'AdminImageWidget' object has no attribute 'decompress'``` – Mrcreamio Dec 14 '21 at 08:11
1

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, ProductImage
from django.contrib.admin.widgets import AdminFileWidget
from django.utils.html import format_html
from django.db import models

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

class ProductImageInline(admin.TabularInline):
    model = ProductImage
    formfield_overrides = {
        models.ImageField: {'widget': CustomAdminFileWidget}
    }

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    inlines = (ProductImageInline,)

Then, you can display inline uploaded images as shown below:

enter image description here

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129