• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Создание страницы для команды Django

Count Zero

coder
Пользователь
Регистрация
18.06.2023
Сообщения
21
Реакции
11
Создание страницы для команды Django

У ModelAdmin есть поле action_form, которое отвечает за вид формы для комманд на странице списка моделей. А вот как настроить страницу подтверждения перед выполнением команды? Как, например, у команды удаления.

Допустим, у нас есть модель с внешней ссылкой, и мы хотим сделать команду группового изменения этой ссылки. Открыл страницу со списокм моделей, выбрал нужные объекты, выбрал команду change_parent, открылась страница выбора нового parent из набора возможных вариантов. Выбрал. Сохранил. Профит. Плюс еще возможность сбросить parent установив None.

Начнем с создания django action.

Python:
@admin.register(models.Location)
class LocationAdmin(admin.ModelAdmin):

    # Тут какой-то еще код настройки админки ...

    # Добавляем новый action на страницу списка моделей.
    actions = ['change_parent']

    # Указанные тут action доступны только текущей моделе.
    # Еще есть глобальный список actions,
    # которые отображаются для всех моделей.

    # Функция action принимает запрос и список выбранных объектов.
    def change_parent(self, request, queryset):

        # Функция будет вызвана минимум дважды.
        # Один раз со страницы списка моделей, второй раз с нашей страницы.
        # И нам нужно как-то отличить эти запросы. Например,
        # через наличие маркера в POST параметрах.
        # Если в POST есть ключ apply, то это наша форма.
        if 'apply' in request.POST:

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

                # Если форма наша, то в параметре new_parent будет натуральное число.
                # Из формы же приходит строка. если строку возможно переделать в int,
                new_parent = int(request.POST['new_parent'])
                # то все будет хорошо, иначе будет исключение.

                # Теперь нужно проверить значение параметра.
                # 0 у нас будет зарезервировано для сброса значения.
                # Тогда если new_parent положительное число,
                if new_parent > 0:

                    # то попробуем найти объект в базе данных с таким первичным ключем.
                    new_parent = models.Location.objects.get(pk=new_parent)

                    # И если нашли, то подготовим сообщение об успехе.
                    message = "Location {} set as new parent on {} Locations".format(
                        new_parent, queryset.count())

                    # А если не найдет, то будет ошибка и мы перейдем в блок except.

                else:
                    # Если new_parent равен нулю (или меньше), то
                    # это значит, что пользователь хочет его убрать.
                    new_parent = None
                    message = "Parent set empty on {} Locations".format(queryset.count())

            except Exception:

                # Если что-то пойдет не так, то нужно показать в логе исключение,
                traceback.print_exc()
                # а для пользователя сделать вид, что ничего не произошло.
                message = "Parent not found!"

            else:
                # А если не было исключений, то выполним полезную нагрузку.
                queryset.update(parent=new_parent)

            # И отправим пользователю сообщение с информацией о результате.
            self.message_user(request, message)
            # И редирект на страницу списка моделей.
            return HttpResponseRedirect(request.get_full_path())
       
        # А вот если apply нет в данных пост запроса,
        # то нужно сформировать форму для пользователя.
        # Чтобы выбрать новый parent.
        context = {

            # Так как мы вотрглись в процесс между отправкой команды
            # и ее фактическим выполнением, то нужно отрисовать на форме,
            # список выбранных пользователем объектов.

            # Поэтому queryset отправляется в контекст.
            'queryset': queryset,

            # Еще нужен набор объектов для нового parent.
            # Да, Я просто выплевываю все и да, так делать плохо.
            # Не делайте так.
            'objects': models.Location.objects.all(),

            # Все что ниже нужно джанге, чтобы найти этот метод,
            # когда пользователь нажмет на кнопку.
            'index': request.POST['index'],
            'action': request.POST['action'],
            'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,

        }

        # И отдаем контекст вместе с формой.
        return TemplateResponse(
            request, 'admin/change_parent_intermediate.html', context=context)

Для команды нужно добавить новый админский шаблон себе в templates/admin. Этот шаблон будет показывать форму выбора нового parent. И отправлять сделанный выбор обратно в команду.

HTML:
{# Расширяем какой-то админский шаблон #}
{% extends "admin/base_site.html" %}

{% block content %}

{# А вот и объявление нашей формы. Параметр action пустой - это означает, что #}
{# запрос будет отправлен на урл из адресной строки. #}
<form action="" method="post">
  {% csrf_token %}

{# Попытка объяснить пользователю, что происходит #}
  <p>Choice new parent for selected Locations:</p>
  <p>

{# Описываем select, который заполнит поле new_parent.#}
      <select name="new_parent">

        {# 0 - зарезервирован для установки пустого значения. #}
        <option value="0" selected>empty</option>

        {% for obj in objects %}
            <option value="{{ obj.id }}">{{ obj }}</option>
          {% endfor %}

      </select>
  </p>

{# Свое сделали, а теперь нужно поместить поля, которые нужны, #}
{# чтобы ModelAdmin смог найти нужный action #}
{# и передать в него выбранные пользователем ранее объекты. #}

{# Для этого и нужен был queryset в контексте. #}
  {% for obj in queryset %}
    <input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
  {% endfor %}

{# И поля, указывающие на выбранный пользователем action. #}
  <input type="hidden" name="index" value="{{ index }}" />
  <input type="hidden" name="action" value="{{ action }}" />

{# Кнопке нужно дать имя маркера, который указывали в action. #}
  <input type="submit" name="apply" value="Set selected as new parent"/>

</form>
{% endblock %}

И это все что нужно.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх