Настройка формы редактирования записей в админ-панели

Курс по Django: https://stepik.org/a/183363

Архив проекта: 41_sitewomen.zip

Продолжаем знакомиться с настройкой админ-панели Django и на этом занятии увидим, как можно управлять внешним видом и функционалом страницы добавления и редактирования отдельных постов.

https://docs.djangoproject.com/en/4.2/ref/contrib/admin/

Если перейти на редактирование какой-либо записи, то фреймворк Django автоматически сформирует страницу с набором всех редактируемых полей, которые мы с вами определяли для модели Women. Мы здесь не видим только поля time_create и time_update, значения которых формируются автоматически.

Набор и свойства отображаемых полей можно настраивать. Например, в классе WomenAdmin можно определить атрибут fields со списком отображаемых на форме полей:

@admin.register(Women)
class WomenAdmin(admin.ModelAdmin):
    fields = ['title', 'content', 'slug']
    ...

Обновляем страницу и видим только эти три поля. Причем поля отображаются в порядке их перечисления в атрибуте fields. Если порядок поменять, например:

fields = ['title', 'slug', 'content']

то поля в форме также поменяют свое положение.

Давайте теперь попробуем добавить новую запись по женщинам в форму только с тремя полями. Например:

title: Екатерина Гусева
slug: ekaterina-guseva
content: Биография Екатерины Гусевой

Нажимаем кнопку «Сохранить», и видим ошибку. Она возникла из-за того, что у нас заполнены не все обязательные поля. В частности, поле «Категория». Но мы и не могли его заполнить, так как его нет в форме. Поэтому здесь, конечно, нужно отображать все обязательные поля. Давайте добавим еще категории:

fields = ['title', 'slug', 'content', 'cat']

Теперь при заполнении и сохранении новой записи у нас не возникает никаких проблем.

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

@admin.register(Women)
class WomenAdmin(admin.ModelAdmin):
#    fields = ['title', 'slug', 'content', 'cat']
    exclude = ['tags', 'is_published']
    ...

На форме увидим все поля, кроме тэгов и статуса.

Далее, мы можем сделать поля только для чтения. Для этого используется коллекция readonly_fields:

@admin.register(Women)
class WomenAdmin(admin.ModelAdmin):
#    fields = ['title', 'slug', 'content', 'cat']
    readonly_fields = ['slug']
    exclude = ['tags', 'is_published']

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

@admin.register(Women)
class WomenAdmin(admin.ModelAdmin):
    fields = ['title', 'slug', 'content', 'cat', 'husband']
    readonly_fields = ['slug']
    ...

Теперь поля следуют в порядке списка fields. Но при добавлении новой записи у нас поле slug первый раз будет пустым, а второй раз запись добавиться не сможет, т.к. нарушается уникальность слага. Поправить это можно двумя способами. Забегая вперед, отмечу, что в момент сохранения записи вызывается метод save() у модели. Это значит, мы можем в модели Women переопределить этот метод и сделать автозаполнение слага:

class Women(models.Model):
    ...
    def save(self, *args, **kwargs):
        self.slug = slugify(self.title , allow_unicode=True)
        super().save(*args, **kwargs)
    ...

Правда, в этом случае slug заполняется русскими символами, что не очень хорошо. При попытке открыть такой пост у нас возникнет ошибка конвертора slug в маршруте.

Исправить это можно с помощью самой известной техники программирования – костыля. Перед классом Women объявим метод, который будет переводить русские символы в латиницу следующим образом:

def translit_to_eng(s: str) -> str:
    d = {'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
         'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i', 'к': 'k',
         'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r',
         'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'h', 'ц': 'c', 'ч': 'ch',
         'ш': 'sh', 'щ': 'shch', 'ь': '', 'ы': 'y', 'ъ': '', 'э': 'r', 'ю': 'yu', 'я': 'ya'}
 
    return "".join(map(lambda x: d[x] if d.get(x, False) else x, s.lower()))

Параметр allow_unicode=True уберем, так как он теперь не нужен, и снова сохраним пост. Увидим слаг, записанный латинскими символами. Теперь мы можем посмотреть статью на сайте без каких-либо ошибок.

Но есть путь гораздо проще, но он применим только для админ-панели. Уберем метод save() из модели Women, а в классе WomenAdmin пропишем следующий атрибут:

class WomenAdmin(admin.ModelAdmin):
    fields = ['title', 'slug', 'content', 'cat', 'husband']
 #  readonly_fields = ['slug']
    prepopulated_fields = {"slug": ("title",)}
    ...

Теперь при изменении поля title, поле slug будет автоматически заполняться корректными латинскими символами, формируя слаг.

Если у вас этот атрибут не сработал, то очистите поля title и slug, нажмите кнопку «Сохранить» и, возможно, это поможет.

В заключение этого занятия давайте посмотрим, как можно работать и настраивать виджет для типа связи многие ко многим. В модели Women за нее отвечает атрибут tags. Выведем ее в форму (в классе WomenAdmin):

fields = ['title', 'slug', 'content', 'cat', 'husband', 'tags']

Откроем запись с проставленными тегами и видим, что они подсвечиваются серым цветом. Мы можем менять их, нажав на кнопку Ctrl и кликая мышкой по этому списку.

Но этот же виджет можно отобразить в форме и по-другому. Если перейти в класс WomenAdmin и прописать атрибут:

filter_horizontal = ['tags']

то увидим расширенное представление для работы со списком тегов. Часто это бывает гораздо удобнее.

Или, по аналогии, можно прописать вертикальный фильтр:

filter_vertical = ['tags']

и в этом случае окошки располагаются по вертикали со всем прежним функционалом.

Вот такие настройки существуют для формирования желаемого вида формы редактирования отдельных записей.

Курс по Django: https://stepik.org/a/183363

Видео по теме