Admin
Администратор
Создание страницы для команды Django
Создание страницы для команды 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 %}
И это все что нужно.