Как добавить пользовательские кнопки в Django Admin
В этой статье «шпаргалке» рассмотрено добавление пользовательских кнопок в интерфейс Django Admin. В первой части рассказано как добавить одно кнопку на страницу списка выбранной модели — list view. Например кнопку импорта чего либо. Во второй части рассказано как добавить пользовательские кнопки действий (actions) для каждой выбранной записи отдельно с дополнительными формами.
Предположим, что нам нужно добавить отдельную кнопку в списочный интерфейс какой нибудь модели. В данном случае предположим, что это будет кнопка импорта данных. По нажатия на которую будет запускать импорт данных.
Для этого нам нужно будет внести изменения в файл admin.py и добавить дополнительный шаблон админки для соответствующей модели.
Начнем с файла шаблона. Наш шаблон будет основан на стандартном шаблоне, мы просто внесем в него небольшое изменение. В папке соответствующего приложения добавим каталоги: xxx/templates/admin/model_change_list.html.
В этот шаблон добавим следующие изменения.
{% extends 'admin/change_list.html' %} {% block object-tools %} <form action="import/" method="POST"> {% csrf_token %} <input type="submit" value="Импорт" /> </form> {{ block.super }} {% endblock %}
В файле admin.py добавим указание на файл этот шаблон. Добавим функцию для регистрации нового URL get_urls. В нее добавим маршрут импорта с указанием на функцию импорта. И далее добавим саму функцию, импорта:
from django.contrib import admin from django.http import HttpResponseRedirect from django.conf.urls import url from monitor.models import LoginMonitor from monitor.import_custom import ImportCustom @admin.register(LoginMonitor)<br> class LoginMonitorAdmin(admin.ModelAdmin):<br> change_list_template = "admin/monitor_change_list.html" def get_urls(self): urls = super(LoginMonitorAdmin, self).get_urls() custom_urls = [ url('^import/$', self.process_import, name='process_import'),] return custom_urls + urls def process_import_btmp(self, request): import_custom = ImportCustom() count = import_custom.import_data() self.message_user(request, f"создано {count} новых записей") return HttpResponseRedirect("../")
Все, для добавление новой кнопки этого достаточно.
Далее будет перевод старой, но все еще актуальной статьи: Haki Benita — How to Add Custom Action Buttons to Django Admin
В последнем посте мы представили шаблон, который мы часто используем в наших моделях Django. Мы использовали приложение по управлению банковского счета с моделями Action и Action, чтобы продемонстрировать, как мы решаем общие проблемы, такие как параллелизм и проверка. У банковского счета было две операции, которые мы хотели выставить в интерфейсе администратора — deposit (добавления на счет) и withdraw (снятие со счета).
Мы собираемся добавить кнопки в интерфейсе администратора Django для ввода и вывода денег со счета, и мы сделаем это менее чем за 100 строк кода!
Как это будет выглядеть в конце?
Почему бы не использовать существующие action в интерфейсе администратора?
Встроенные action в интерфейса администратора работают с набором запросов queryset. Они скрыты в выпадающем меню на верхней панели инструментов и в основном полезны для выполнения массовых операций. Хорошим примером является действие по умолчанию. Вы отмечаете несколько строк и выбираете «удалить строки» из выпадающего меню. Это не очень быстро и не всегда подходит для некоторых случаев использования.
Другим недостатком является то, что действия не доступны в detail view. Чтобы добавить кнопки в подробный вид, вам нужно переопределить шаблон.
Формы
Прежде всего, нам нужно получить дополнительные данные от пользователя для выполнения действия. Естественно для этого, нам нужна форма. Нам нужна одна форма для deposit и одна форма для withdraw.
В дополнение к выполнению действия мы также добавим опцию, возможность отправить уведомление по электронной почте владельцу учетной записи, сообщая ему о действии, выполненном в его учетной записи.
Все наши actions имеют общие аргументы, такие как comment и send_email. Actions также обрабатывают успех и неудачу аналогичным образом.
Давайте начнем с базовой формы для обработки общего action:
# forms.py from django import forms from common.utils import send_email from . import errors class AccountActionForm(forms.Form): comment = forms.CharField( required=False, widget=forms.Textarea, ) send_email = forms.BooleanField( required=False, ) @property def email_subject_template(self): return 'email/account/notification_subject.txt' @property def email_body_template(self): raise NotImplementedError() def form_action(self, account, user): raise NotImplementedError() def save(self, account, user): try: account, action = self.form_action(account, user) except errors.Error as e: error_message = str(e) self.add_error(None, error_message) raise if self.cleaned_data.get('send_email', False): send_email( to=[account.user.email], subject_template=self.email_subject_template, body_template=self.email_body_template, context={ "account": account, "action": action, } ) return account, action
- Каждое action имеет комментарий comment и возможность отправить уведомление, если действие выполнено успешно.
- Фактическая операция выполняется при сохранении формы. Это похоже на то, как работает ModelForm.
- В целях ведения журнала и аудита вызывающая сторона должна предоставить пользователя, выполняющего action.
- Обязательные свойства, которые не реализованы производными формами, вызовут NotImplementedError. Таким образом, мы гарантируем, что разработчик получит информативное сообщение об ошибке, если он забудет что-то реализовать.
- Ошибки обрабатываются на уровне базового класса исключений. Наши модели определяют базовый класс ошибок, поэтому мы можем легко перехватить все (и только) исключения, связанные с аккаунтом, и обработать их соответствующим образом.
Теперь, когда у нас есть простой базовый класс, давайте добавим форму для withdraw. Для withdraw нам нужно добавить поле суммы:
# forms.py from django.utils import timezone from .models import Account, Action class WithdrawForm(AccountActionForm): amount = forms.IntegerField( min_value=Account.MIN_WITHDRAW, max_value=Account.MAX_WITHDRAW, required=True, help_text='How much to withdraw?', ) email_body_template = 'email/account/withdraw.txt' field_order = ( 'amount', 'comment', 'send_email', ) def form_action(self, account, user): return Account.withdraw( id=account.pk, user=account.user, amount=self.cleaned_data['amount'], withdrawn_by=user, comment=self.cleaned_data['comment'], asof=timezone.now(), )
- Мы расширили базу AccountActionForm и добавили поле amount с соответствующими проверками.
- Мы заполнили необходимые атрибуты, email_body_template.
- Мы реализовали action формы, используя classmethod из предыдущего поста. Модель заботится о блокировке записи, обновлении любых вычисляемых полей и добавлении соответствующих действий в журнал.
Следующим шагом является добавление deposit. Для deposit требуются поля amount, reference и reference_type:
# forms.py class DepositForm(AccountActionForm): amount = forms.IntegerField( min_value=Account.MIN_DEPOSIT, max_value=Account.MAX_DEPOSIT, required=True, help_text='How much to deposit?', ) reference_type = forms.ChoiceField( required=True, choices=Action.REFERENCE_TYPE_CHOICES, ) reference = forms.CharField( required=False, ) email_body_template = 'email/account/deposit.txt' field_order = ( 'amount', 'reference_type', 'reference', 'comment', 'send_email', ) def form_action(self, account, user): return Account.deposit( id=account.pk, user=account.user, amount=self.cleaned_data['amount'], deposited_by=user, reference=self.cleaned_data['reference'], reference_type=self.cleaned_data['reference_type'], comment=self.cleaned_data['comment'], asof=timezone.now(), )
На данный момент мы получили необходимые формы для принятия, проверки и оформления deposit и withdraw. Следующим шагом является его интеграция в представление списка администратора Django.
Admin
Прежде чем мы сможем добавить кнопки для actions, нам нужно настроить базовую страницу администратора для нашей модели Account:
# admin.py from django.contrib import admin from .models import Account @admin.register(Account) class AccountAdmin(admin.ModelAdmin): date_heirarchy = ( 'modified', ) list_display = ( 'id', 'user', 'modified', 'balance', 'account_actions', ) readonly_fields = ( 'id', 'user', 'modified', 'balance', 'account_actions', ) list_select_related = ( 'user', ) def account_actions(self, obj): # TODO: Render action buttons
Примечание: мы можем сделать просмотр списка намного лучше, добавив ссылку на пользователя и actions учетной записи. Мы можем добавить несколько полей поиска и многое другое. Ранее я писал о проблемах производительности в интерфейсе администратора при масштабировании приложения Django для сотен тысяч пользователей. Там есть несколько приятных трюков, которые могут сделать даже этот простой вид намного приятнее.
Добавление кнопок Action
Мы хотим добавить кнопки action для каждой учетной записи и сделать так, чтобы они ссылались на страницу с формой. В Django есть функция для регистрации URL get_urls в виде списка. Давайте использовать эту функцию, чтобы добавить маршруты для наших пользовательских действий:
# admin.py from django.utils.html import format_html from django.core.urlresolvers import reverse class AccountAdmin(admin.ModelAdmin): # ... def get_urls(self): urls = super().get_urls() custom_urls = [ url( r'^(?P<account_id>.+)/deposit/$', self.admin_site.admin_view(self.process_deposit), name='account-deposit', ), url( r'^(?P<account_id>.+)/withdraw/$', self.admin_site.admin_view(self.process_withdraw), name='account-withdraw', ), ] return custom_urls + urls def account_actions(self, obj): return format_html( '<a class="button" href="{}">Deposit</a> ' '<a class="button" href="{}">Withdraw</a>', reverse('admin:account-deposit', args=[obj.pk]), reverse('admin:account-withdraw', args=[obj.pk]), ) account_actions.short_description = 'Account Actions' account_actions.allow_tags = True def process_deposit(self): # TODO def process_withdraw(self): # TODO
- Мы зарегистрировали два URL, один для deposit и один для withdraw.
- Мы сослались на каждый маршрут на соответствующие функции process_deposit и process_withdraw. Эти функции отобразят промежуточную страницу с соответствующей формой и выполнят операцию.
- Мы добавили настраиваемое поле account_actions, чтобы отображать кнопки для каждого действия. Преимущество использования «обычного» поля администратора, такого как account_actions, заключается в том, что оно доступно как в подробностях (detail view), так и в виде списка (list view).
Давайте перейдем к реализации функций для обработки actions:
# admin.py from django.http import HttpResponseRedirect from django.template.response import TemplateResponse from .forms import DepositForm, WithdrawForm class AccountAdmin(admin.ModelAdmin): # ... def process_deposit(self, request, account_id, *args, **kwargs): return self.process_action( request=request, account_id=account_id, action_form=DepositForm, action_title='Deposit', ) def process_withdraw(self, request, account_id, *args, **kwargs): return self.process_action( request=request, account_id=account_id, action_form=WithdrawForm, action_title='Withdraw', ) def process_action( self, request, account_id, action_form, action_title ): account = self.get_object(request, account_id) if request.method != 'POST': form = action_form() else: form = action_form(request.POST) if form.is_valid(): try: form.save(account, request.user) except errors.Error as e: # If save() raised, the form will a have a non # field error containing an informative message. pass else: self.message_user(request, 'Success') url = reverse( 'admin:account_account_change', args=[account.pk], current_app=self.admin_site.name, ) return HttpResponseRedirect(url) context = self.admin_site.each_context(request) context['opts'] = self.model._meta context['form'] = form context['account'] = account context['title'] = action_title return TemplateResponse( request, 'admin/account/account_action.html', context, )
Deposit и withdraw осуществляются очень похожим образом. Чтобы выполнить оба действия, мы хотим отобразить форму на промежуточной странице и выполнить действие, когда пользователь отправит форму.
process_action обрабатывает отправку формы для обоих действий. Функция принимает форму, название действия и идентификатор учетной записи. process_withdraw и process_deposit, используются для установки соответствующего контекста для каждой операции.
Для завершения процесса нам нужен template для промежуточной страницы, который содержит форму действия. Мы собираемся основывать наш шаблон на существующем шаблоне, используемом Django Admin:
<!-- templates/admin/account/account_action.html --> {% extends "admin/change_form.html" %} {% load i18n admin_static admin_modify %} {% block content %} <div id="content-main"> <form action="" method="POST"> {% csrf_token %} {% if form.non_field_errors|length > 0 %} <p class="errornote"> "Please correct the errors below." </p> {{ form.non_field_errors }} {% endif %} <fieldset class="module aligned"> {% for field in form %} <div class="form-row"> {{ field.errors }} {{ field.label_tag }} {{ field }} {% if field.field.help_text %} <p class="help"> {{ field.field.help_text|safe }} </p> {% endif %} </div> {% endfor %} </fieldset> <div class="submit-row"> <input type="submit" class="default" value="Submit"> </div> </form> </div> {% endblock %}
И это все!
Теперь наши сотрудники могут легко вносить и снимать средства прямо из интерфейса администратора. Не нужно создавать дорогую панель инструментов.
Я обещал, что мы сделаем это в 100 строк, и мы сделали это за меньше количество!
Заключение
Большая часть реализации взята из превосходного (превосходного!) Пакета django-import-export. Это сэкономило нам часы «Вы можете просто отправить мне данные в Excel?» и мы любим это за это. Если вы не знакомы с этим, вы должны обязательно проверить это.
def account_actions(self, obj):
return ‘<a class=»button» href=»{}»>Print</a>’.format(‘y’)
account_actions.short_description = ‘Account Actions’
account_actions.allow_tags = True
этот код у меня в Django aadmin отображается так
<a class=»button» href=»y»>Print</a>
а хотелось бы кнопку. Подскажите
не корректно скопировал, уточняю.
def button(self, obj):
return ‘<a class=»button» href=»{}»>Print</a>’.format(‘y’)
button.short_description = ‘Actions’
button.allow_tags = True
Вы можете использовать тег button вместо тега a
типа так:
<button class=»button»>Print</button>
Или задать нужный внешний вид тега a через класс button
return ‘<button class=»button»>Print</button>’.format(‘y’)
результат этой строки
так и выходит в таблице, кнопки нет.
<button class=»button»>Print</button>
Вот тут возможное описание решения проблемы https://stackoverflow.com/questions/47953705/how-do-i-use-allow-tags-in-django-2-0-admin.
Вместо allow_tags нужно использовать mark_safe
кнопка появилась только после
from django.utils.safestring import mark_safe
…
def button(self, obj):
return mark_safe(‘<button class=»button» href=»{}»>example</button>’.format(obj.pk))
xxx/templates/admin/model_change_list.html
change_list_template = «admin/monitor_change_list.html»
Статья хорошая, но скопирована без проверки.
Может кому поможет:
https://stackoverflow.com/questions/28777376/problems-extend-change-form-html-in-django-admin/28777461#28777461