Как да добавите персонализирани бутони за действие към администратора на Django

За по-добро преживяване при четене, разгледайте тази статия на моя уебсайт.

Ние сме големи фенове на администраторския интерфейс на Django. Това е огромна продажна точка за Django, тъй като отнема натоварването при разработването на „бек офис“ за поддръжка и ежедневни операции.

В последната публикация представихме модел, който използваме често в нашите модели Django. Използвахме приложение за банкова сметка с модели на акаунт и акаунт, за да демонстрираме начина, по който се справяме с често срещани проблеми като паралелност и валидиране. Банковата сметка имаше две операции, които искахме да изложим в администраторския интерфейс - депозиране и теглене.

Ще добавим бутони в администраторския интерфейс на Django, за да депозираме и изтеглим от акаунт, и ще го направим в по-малко от 100 реда код!

Как изглежда?

Административен интерфейс на Django с персонализирани бутони за действие

Нашите персонализирани действия са приятно изглеждащите депозит и теглене на бутони до всеки акаунт.

Защо да не използвате съществуващите административни действия?

Вградените действия на администратора работят на набор от заявки. Те са скрити в падащото меню в горната лента с инструменти и са предимно полезни за изпълнение на групови операции. Добър пример е действието за изтриване по подразбиране - маркирайте колкото искате редове и натиснете изтриване - това не е нашия случай.

Django вградени в действия

Друг недостатък от използването на действията на Django е, че действията не са достъпни в детайлния изглед. За да добавите бутони към изгледа с детайли, трябва да отмените шаблона - огромна болка и обикновено не си струва.

Формите

Първо нещо първо - имаме нужда от някои данни от потребителя, за да извършим действието така естествено, имаме нужда от формуляр - един за депозит и един за теглене.

В допълнение към извършването на действието ще добавим изящна опция да изпратим известие по имейл до собственика на акаунта, който да го информира за извършено действие към неговия акаунт.

Всички наши действия имат общи аргументи (коментар, send_email) и те обработват успеха и неуспеха по подобен начин.

Нека започнем с основен формуляр за справяне с общо действие по акаунта:

# form.py
от формите за внос на django
от common.utils импортира send_email
от. грешки при импортиране
клас AccountActionForm (form.Form):
    коментар = форми.CharField (
        изисква = False,
        приспособление = forms.Textarea,
    )
    send_email = форми.BooleanField (
        изисква = False,
    )
    @Имот
    дефинирайте email_subject_template (самостоятелно):
        върнете „имейл / акаунт / уведомление_subject.txt“
    @Имот
    def email_body_template (самостоятелно):
        повишаване NotImplementedError ()
    дефиниране на формуляр (самостоятелно, акаунт, потребител):
        повишаване NotImplementedError ()
    def save (самостоятелно, акаунт, потребител):
        опитвам:
            акаунт, действие = само.формиране (акаунт, потребител)
        с изключение на грешки. Грешка като e:
            error_message = str (e)
            self.add_error (Няма, съобщение за грешка)
            повишаване на
        ако self.cleaned_data.get ('send_email', Грешно):
            Изпратете имейл(
                за = [account.user.email],
                subject_template = self.email_subject_template,
                body_template = self.email_body_template,
                контекст = {
                    "акаунт": акаунт,
                    "действие": действие,
                }
            )
    връщане на сметка, действие

И така, какво имаме тук:

  • Всяко действие има коментар и опция за изпращане на известие, ако действието приключи успешно.
  • Подобно на ModelForm, ние изпълняваме операцията във функцията за запазване.
  • Обаждащият се трябва да посочи потребителя, който изпълнява действието за целите на регистриране и одит.
  • Ние повишаваме NotImplementedError за необходимите свойства, за да сме сигурни, че получаваме хубаво съобщение за грешка, ако забравим да ги отменим.
  • Използвахме базово изключение в нашите модели, за да можем да хванем всички изключения, свързани с профила, и да ги обработваме по подходящ начин.

Сега, когато имаме прост базов клас, нека добавим формуляр за изтегляне от акаунт. Единственото допълнително поле е сумата за теглене:

# form.py
от django.utils импортира часовата зона
от .models import Account, Action
клас WithdrawForm (AccountActionForm):
    количество = форми.IntegerField (
        MIN_VALUE = Account.MIN_WITHDRAW,
        MAX_VALUE = Account.MAX_WITHDRAW,
        изисква = Вярно е,
        help_text = 'Колко да изтеглите?',
    )
    email_body_template = 'имейл / акаунт / теглене.txt'
    field_order = (
        'количество',
        "Коментар",
        'Изпратете имейл',
    )
    дефиниране на формуляр (самостоятелно, акаунт, потребител):
        връщане Account.withdraw (
            ID = account.pk,
            потребител = account.user,
            количество = self.cleaned_data [ "количество"],
            withdrawn_by = употреба,
            коментар = self.cleaned_data [ 'коментар "],
            asof = timezone.now (),
        )

Доста направо:

  • Добавихме допълнителното поле (сума) с подходящите валидации.
  • Въведете необходимите атрибути (шаблон на тялото на имейл).
  • Реализирайте действието на формата, използвайки classmethod от предишния пост. Методът се грижи за заключване на записа, актуализиране на всички изчислени полета и добавяне на правилното действие към дневника.

Действието за депозит има допълнителни полета - референтен и референтен тип:

# form.py
клас Депозитна форма (AccountActionForm):
    количество = форми.IntegerField (
        MIN_VALUE = Account.MIN_DEPOSIT,
        MAX_VALUE = Account.MAX_DEPOSIT,
        изисква = Вярно е,
        help_text = „Колко да депозирате?“,
    )
    reference_type = forms.ChoiceField (
        изисква = Вярно е,
        избор = Action.REFERENCE_TYPE_CHOICES,
    )
    reference = form.CharField (
        изисква = False,
    )
    email_body_template = 'имейл / акаунт / deposit.txt'
    field_order = (
        'количество',
        "Reference_type"
        "Референтна"
        "Коментар",
        'Изпратете имейл',
    )
    дефиниране на формуляр (самостоятелно, акаунт, потребител):
        върнете Account.deposit (
            ID = account.pk,
            потребител = account.user,
            количество = self.cleaned_data [ "количество"],
            deposited_by = употреба,
            позоваване = self.cleaned_data [ "референтен"],
            reference_type = self.cleaned_data [ 'reference_type "],
            коментар = self.cleaned_data [ 'коментар "],
            asof = timezone.now (),
        )

Сладка!

Администраторът

Преди да добавим фантазирани бутони, трябва да настроим основна страница на администратора за нашия модел на акаунт:

# admin.py
от django.contrib администратор за импортиране
от .models account import
@ Admin.register (профил)
клас AccountAdmin (admin.ModelAdmin):
    date_heirarchy = (
        "Модифициран",
    )
    list_display = (
        'документ за самоличност',
        "Потребител",
        "Модифициран",
        "Баланс",
        "account_actions",
    )
    readonly_fields = (
        'документ за самоличност',
        "Потребител",
        "Модифициран",
        "Баланс",
        "account_actions",
    )
    list_select_related = (
        "Потребител",
    )
     
    def account_action (self, obj):
        # TODO: Бутони за възпроизвеждане на действие

Странична бележка: Можем да направим изгледа на списъка много по-добър - добавете линк към потребителя и действията на акаунта, добавете полета за търсене и много други, но тази публикация не е за това. По-рано писах за съображения за производителност в администраторския интерфейс при мащабиране на приложението Django на стотици хиляди потребители и има някои хубави трикове там, които могат да направят дори този прост изглед много по-хубав.

Добавяне на бутоните за действие

Искаме да добавим бутони за действие за всеки акаунт и да ги свържем към страница с формуляр. За щастие, Django има функция за добавяне на URL, така че нека го използваме за добавяне на маршрутите и съответните бутони:

# admin.py
от django.utils.html import format_html
от django.core.urlresolvers импорт обратно
клас AccountAdmin (admin.ModelAdmin):
    ...
    def get_urls (self):
        urls = super (). get_urls ()
        custom_urls = [
            URL (
                R '^ (? P . +) / депозит / $',
                self.admin_site.admin_view (self.process_deposit),
                име = 'сметка депозит ",
            ),
            URL (
                R '^ (? P . +) / изтегли / $',
                self.admin_site.admin_view (self.process_withdraw),
                име = 'сметката оттегли ",
            ),
        ]
        върнете custom_urls + URL адреси
    def account_action (self, obj):
        връщане формат_html (
            ' Депозиране  & nbsp;'
            ' Оттегляне  “,
            обратен ('admin: account-deposit', args = [obj.pk]),
            обратен ("администратор: изтегляне на акаунт", args = [obj.pk]),
        )
    account_action.short_description = 'Действия в акаунта'
    account_action.allow_tags = Вярно

Извършваме два бутона, всеки от които се свързва към изглед, който изпълнява съответната функция process_deposit / изтегляне. Двата изгледа ще представят междинна страница със съответния формуляр. Когато формулярът бъде изпратен, изгледът ще се пренасочи обратно към нашата подробна страница или ще информира потребителя за грешка.

Хубава характеристика на използването на полето account_action е, че е достъпно както в детайла, така и в списъка, тъй като е редовно поле в администратора.

Функцията, която обработва действителното действие:

# admin.py
от django.http import HttpResponseRedirect
от django.template.response импортиране TemplateResponse
от .forms импортира DepositForm, WithdrawForm
клас AccountAdmin (admin.ModelAdmin):
   ...
   дефиниране_процес (самостоятелно, заявка, акаунт_ид, * аргументи, ** kwargs):
        върнете самостоятелно обработване на процеса (
            поискване = поискване
            ACCOUNT_ID = ACCOUNT_ID,
            action_form = DepositForm,
            action_title = 'Депозит',
        )
   дефинирай_proces_withdraw (самостоятелно, заявка, account_id, * args, ** kwargs):
        върнете самостоятелно обработване на процеса (
            поискване = поискване
            ACCOUNT_ID = ACCOUNT_ID,
            action_form = WithdrawForm,
            action_title = 'оттегли',
        )
     
   дефиниране на процесите (
        себе си,
        поискване,
        ACCOUNT_ID,
        action_form,
        action_title
   ):
        account = self.get_object (заявка, account_id)
        if request.method! = 'POST':
            форма = действие_форма ()
        друго:
            форма = действие_форма (заявка.POST)
            ако form.is_valid ():
                опитвам:
                    form.save (акаунт, заявка.user)
                с изключение на грешки. Грешка като e:
                    # Ако save () е повдигнато, формулярът ще има non
                    # полева грешка, съдържаща информационно съобщение.
                    минавам
                друго:
                    self.message_user (заявка, „Успех“)
                    url = обратен (
                        "Админ: account_account_change"
                       опцията = [account.pk],
                        CURRENT_APP = self.admin_site.name,
                    )
                    върнете HttpResponseRedirect (URL)
        контекст = self.admin_site.each_context (заявка)
        контекст ['opts'] = self.model._meta
        контекст ['форма'] = форма
        контекст ['акаунт'] = акаунт
        контекст ['заглавие'] = action_title
        върнете TemplateResponse (
            поискване,
            "Администратор / сметка / account_action.html"
            контекст,
        )

Написахме функция, наречена обработка на процеси, която приема формата, заглавието на действието и идентификатора на акаунта и обработва подаването на формуляра. Двете функции, process_withdraw и process_deposit, се използват за задаване на съответния контекст за всяка операция.

Тук има някои административни котлони на Django, които се изискват от администраторския сайт на Django. Няма смисъл да копаем твърде дълбоко в него, защото това не е от значение за нас в този момент.

Остава само да добавите шаблона на междинната страница, съдържащ формата. За пореден път, не е необходимо да се работи твърде усилено - Django вече има шаблон за подробна страница, който можем да разширим:

# шаблони / администратор / акаунт / акаунт washing.html
{% extends "admin / change_form.html"%}
{% load i18n admin_static admin_modify%}
{% блокиране на съдържание%}
  
    {% csrf_token%}
    {% if form.non_field_errors | дължина> 0%}
      

          "Моля коригирайте грешките по-долу."              {{form.non_field_errors}}     {% endif%}

    
      {% за поле под формата%}         
          {{field.errors}}           {{field.label_tag}}           {{field}}           {% if field.field.help_text%}           

            {{field.field.help_text | безопасно}}                      {% endif%}                {% endfor%}     

    
           
  
{% endblock%}

Това е то!

Членовете на персонала вече могат лесно да депозират и изтеглят директно от администраторския интерфейс. Няма нужда да създавате скъпо табло или ssh към сървъра.

Обещах, че ще го направим в 100 реда и го направихме за по-малко!

Печалба!

Кредити

Големи части от реализацията са взети от отличния (отличен!) Пакет django-import-export. Това ни спести часове от „Можете ли просто да ми изпратите данните в Excel?“ И ние го обичаме за това. Ако не сте запознати с него, определено трябва да го проверите.

Къде можем да го вземем от тук

Сега, когато заковахме тази техника, можем почти да правим с нея каквото си поискаме - имаме пълен контрол над маршрута и какво се прави. Възможно е също да добавите бутони за действие, които не изискват допълнителни данни без междинната страница.

Следващата стъпка ще бъде да се абстрахира тази функционалност и да се сложи миксин, но ние ще преминем този мост, когато стигнем до там.