Превью для ImageField в админке

Этот туториал поможет вам прокачать админку сайта с блогом. У каждого поста есть картинка, но в админке отображается только её название, а хочется увидеть, как она выглядит. Вы прикрутите к админке превью картинок, выглядеть будет примерно так:

Админка

Перед туториалом

В этом туториале подразумевается, что вы уже умеете работать с админкой джанги, а чтение моделей не вызывает у вас проблем.

Ссылки на материалы для подготовки:

1. Запустите сайт

Разверните у себя этот блог, чтобы продолжить.

Это блог создан в учебных целях для урока, где мы учим оптимизации Django, поэтому он немного странный и медленный.

Первым делом запустите проект, следуя инструкциям в README. Если всё сделано правильно, то по адресу http://127.0.0.1:8000/ вы увидите работающий сайт:

Сайт

Сайт пока работает с пустой базой данных и выглядит совсем пустым. Исправьте это. Создайте суперпользователя, чтобы Django пустила вас в админку. Используйте команду createsuperuser:

$ python3 manage.py createsuperuser --username root

После зайдите в админку по адресу http://127.0.0.1:8000/admin и добавьте две записи: один тег и один пост:

Редактирование поста

2. Добавьте посту новое поле в админке

По умолчанию админка показывает все поля модели данных. Но при желании это можно изменить: в админке можно добавить новые поля и надписи, которых не было у модели.

Для того, чтобы в админке появилось превью картинок поста, как раз пригодится такой приём: превью будет дополнительной формой в админке.

Новое поле можно создать в ModelAdmin простым добавлением нового метода:

class PostAdmin(admin.ModelAdmin):

    readonly_fields = ["preview"]

    def preview(self, obj):
        return "Hello!"

admin.site.register(Post, PostAdmin)

Новый метод обязательно нужно добавить в readonly_fields, чтобы он отобразился в админке. Вот что у нас получилось:

картинка

3. Добавьте картинку

Теперь, вместо бесполезного текста можно поставить картинку. Пока что любую из интернета, а потом уже заменим её на картинку, что залита в ImageField:

class PostAdmin(admin.ModelAdmin):

    readonly_fields = ["preview"]

    def preview(self, obj):
        return '<img src="https://dvmn.org/filer/canonical/1591892373/669/">'

Получился неожиданный результат, вместо HTML-тега с картинкой вставился этот же тег, но текстом:

картинка

Оказывается, админка не хочет добавлять абы что. В ней есть защита от таких вот HTML-включений и она заранее экранировала все символы так, чтобы теги преобразовались в текст.

Чтобы всё же добавить именно тег, нужно явно сказать админке, что строка с тегами безопасная. Это умеет делать метод mark_safe():

from django.utils.safestring import mark_safe


class PostAdmin(admin.ModelAdmin):

    readonly_fields = ["preview"]

    def preview(self, obj):
        return mark_safe("<img src='https://dvmn.org/filer/canonical/1591892373/669/'>")

Теперь картинка добавилась как надо:

картинка

4. Замените статичную картинку на картинку из поля

От превью не будет никакого толку, если оно будет показывать какую-то статичную картинку. Превью должно показывать картинку из ImageField этой модели. Но как же до неё добраться?

Метод preview получает два аргумента: self и obj. Первый — это сам класс PostAdmin, а obj — это объект, на который вы смотрите в админке. То есть это и есть пост. Теперь можно достать из него картинку, а у неё получить url:

from django.utils.safestring import mark_safe


class PostAdmin(admin.ModelAdmin):

    readonly_fields = ["preview"]

    def preview(self, obj):
        return mark_safe(f'<img src="{obj.image.url}">')

Получилось превью, но радоваться победе пока рано, есть ещё несколько вещей, которые стоит сделать:

картинка

5. Перенесите превью к полю с картинкой

Порядок полей в админке тоже можно контролировать. По умолчанию все поля располагаются в таком порядке, в каком они объявлены в models.py. Но его можно поменять: достаточно определить параметр fields и перчислить поля модели в том порядке, в котором вы хотите. Мы расположили так:

class PostAdmin(admin.ModelAdmin):
    fields = ['title', 'slug', 'author', 'image', 'preview',
              'text', 'tags', 'published_at', 'likes']
    readonly_fields = ["preview"]

    def preview(self, obj):
        return mark_safe(f'<img src="{obj.image.url}">')

Как это выглядит:

картинка

6. Измените размер превью

Размер картинки можно менять через стили тега <img>:

    def preview(self, obj):
        return mark_safe(f'<img src="{obj.image.url}" style="max-height: 200px;">')

В данном примере мы ограничили только высоту картинки до 200 пикселей. Благодаря этому она сама перемасштабируется так, чтобы высота не превышала 200 пикселей, но она не сжалась ни горизонтально, ни вертикально:

картинка

Что делать, если ошибка

В процессе написания туториала у нас сломался с виду рабочий код:

from django.utils.safestring import mark_safe


class PostAdmin(admin.ModelAdmin):

    readonly_fields = ["preview"]

    def preview(self, obj):
        return mark_safe('<img src="{obj.image.url}">')

Вместо превью отрисовалась поломанная картинка:

картинка

Вместо разглядывания кода можно открыть Chrome Dev Tools и посмотреть что сломалось:

картинка

Оказалось, что в f-строке не хватало буквы f:

'<img src="{obj.image.url}">'
vs.
f'<img src="{obj.image.url}">'

Попробуйте бесплатные уроки по Python

Получите крутое код-ревью от практикующих программистов с разбором ошибок и рекомендациями, на что обратить внимание — бесплатно.

Переходите на страницу учебных модулей «Девмана» и выбирайте тему.