Jak přidat tlačítka vlastní akce do Django Admin

Pro lepší zážitek ze čtení si přečtěte tento článek na mém webu.

Jsme velcí fanoušci administrátorského rozhraní Django. Je to obrovský prodejní bod pro Django, protože uvolňuje zátěž a vytváří „back office“ pro podporu a každodenní provoz.

V posledním příspěvku jsme představili vzor, ​​který často používáme v našich modelech Django. Použili jsme aplikaci bankovního účtu s modely účtů a účtů, abychom předvedli, jak řešíme běžné problémy, jako je souběžnost a ověření. Bankovní účet měl dvě operace, které jsme chtěli vystavit v administrátorském rozhraní - vklad a výběr.

Přidáme tlačítka do administrátorského rozhraní Django pro vklad a výběr z účtu a uděláme to v méně než 100 řádcích kódu!

Jak to vypadá?

Rozhraní admin Django s tlačítky vlastní akce

Naše vlastní akce jsou pěkně vypadající vklad a tlačítka pro výběr u každého účtu.

Proč nepoužít existující akce správce?

Předdefinované akce správce pracují na sadě dotazů. Jsou skryty v rozevírací nabídce na horním panelu nástrojů a jsou většinou užitečné pro provádění hromadných operací. Dobrým příkladem je výchozí akce odstranění - označte tolik řádků, kolik chcete, a stiskněte tlačítko Odstranit - toto není náš případ.

Django postavený v akcích

Další nevýhodou použití akcí Django je to, že akce nejsou k dispozici v podrobném zobrazení. Chcete-li přidat tlačítka do podrobného zobrazení, musíte šablonu přepsat - obrovskou bolest a obvykle za to nestojí.

Formuláře

První věc první - potřebujeme určitá data od uživatele, abychom mohli akci provést, takže samozřejmě potřebujeme formulář - jeden pro vklad a jeden pro výběr.

Kromě provedení akce přidáme šikovnou možnost zaslat e-mail s oznámením majiteli účtu, který ho informuje o akci provedené na jeho účtu.

Všechny naše akce mají společné argumenty (komentář, send_email) a podobným způsobem řeší úspěch a neúspěch.

Začněme základním formulářem pro zpracování obecné akce na účtu:

# forms.py
z importních formulářů django
z common.utils import send_email
z . chyby importu
třída AccountActionForm (forms.Form):
    comment = forms.CharField (
        požadované = Falešné,
        widget = forms.Textarea,
    )
    send_email = forms.BooleanField (
        požadované = Falešné,
    )
    @vlastnictví
    def email_subject_template (self):
        návrat 'email / account / notification_subject.txt'
    @vlastnictví
    def email_body_template (self):
        zvýšit NotImplementedError ()
    def form_action (self, account, user):
        zvýšit NotImplementedError ()
    def save (self, account, user):
        Snaž se:
            účet, action = self.form_action (účet, uživatel)
        kromě chyb.Orr jako e:
            error_message = str (e)
            self.add_error (None, error_message)
            vyzdvihnout
        pokud self.cleaned_data.get ('send_email', False):
            poslat e-mailem(
                do = [account.user.email],
                subject_template = self.email_subject_template,
                body_template = self.email_body_template,
                context = {
                    "účet": účet,
                    "akce": akce,
                }
            )
    návratový účet, akce

Co tady tedy máme:

  • Každá akce má komentář a možnost odeslat upozornění, pokud byla akce úspěšně dokončena.
  • Podobně jako u ModelForm provádíme operaci ve funkci uložení.
  • Volající musí určit uživatele provádějícího akci pro účely protokolování a auditu.
  • Vyžadujeme NotImplementedError pro požadované vlastnosti, abychom se ujistili, že dostaneme pěknou chybovou zprávu, pokud je zapomeneme přepsat.
  • V našich modelech jsme použili základní výjimku, abychom mohli zachytit všechny výjimky týkající se účtu a náležitě s nimi zacházet.

Nyní, když máme jednoduchou základní třídu, přidáme formulář pro výběr z účtu. Jediným dalším polem je částka k výběru:

# forms.py
z django.utils import časového pásma
z importního účtu .models, Action
třída WithdrawForm (AccountActionForm):
    amount = forms.IntegerField (
        min_value = Účet.MIN_WITHDRAW,
        max_value = Account.MAX_WITHDRAW,
        požadované = Pravda,
        help_text = 'Kolik vybrat?',
    )
    email_body_template = 'email / account / draw.txt'
    field_order = (
        'množství',
        'komentář',
        'poslat e-mailem',
    )
    def form_action (self, account, user):
        return Account.withdraw (
            id = account.pk,
            user = account.user,
            amount = self.cleaned_data ['amount'],
            odebráno_by = uživatel,
            comment = self.cleaned_data ['comment'],
            asof = timezone.now (),
        )

Docela rovně:

  • Přidali jsme další pole (množství) se správnými validacemi.
  • Zadejte požadované atributy (šablona těla e-mailu).
  • Implementace akce formuláře pomocí metody metodu z předchozího příspěvku. Metoda se stará o uzamčení záznamu, aktualizaci všech vypočtených polí a přidání správné akce do protokolu.

Akce vkladu obsahuje další pole - odkaz a typ odkazu:

# forms.py
třída DepositForm (AccountActionForm):
    amount = forms.IntegerField (
        min_value = Účet.MIN_DEPOSIT,
        max_value = Account.MAX_DEPOSIT,
        požadované = Pravda,
        help_text = 'Kolik vložit?',
    )
    reference_type = forms.ChoiceField (
        požadované = Pravda,
        choices = Action.REFERENCE_TYPE_CHOICES,
    )
    reference = forms.CharField (
        požadované = Falešné,
    )
    email_body_template = 'email / account / deposit.txt'
    field_order = (
        'množství',
        'reference_type',
        'odkaz',
        'komentář',
        'poslat e-mailem',
    )
    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 (),
        )

Sladký!

Správce

Než přidáme efektní tlačítka, musíme pro náš model účtu nastavit základní stránku pro správu:

# admin.py
ze souboru django.contrib import admin
z účtu importu .models
@ admin.register (účet)
třída AccountAdmin (admin.ModelAdmin):
    date_heirarchy = (
        „upraveno“,
    )
    list_display = (
        'id',
        'uživatel',
        „upraveno“,
        'Zůstatek',
        'account_actions',
    )
    readonly_fields = (
        'id',
        'uživatel',
        „upraveno“,
        'Zůstatek',
        'account_actions',
    )
    list_select_related = (
        'uživatel',
    )
     
    def account_actions (self, obj):
        # TODO: Vykreslení akčních tlačítek

Vedlejší poznámka: Můžeme vylepšit zobrazení seznamu - přidat odkaz na uživatele a na akce účtu, přidat vyhledávací pole a mnoho dalších, ale tento příspěvek se netýká. Dříve jsem psal o úvahách o výkonu v administrátorském rozhraní při škálování aplikace Django na stovky tisíc uživatelů a tam jsou některé pěkné triky, které mohou i tento jednoduchý pohled udělat mnohem hezčí.

Přidání akčních tlačítek

Chceme přidat akční tlačítka pro každý účet a nechat je odkazovat na stránku s formulářem. Naštěstí má Django funkci pro přidání URL, takže ji použijeme k přidání tras a odpovídajících tlačítek:

# admin.py
z django.utils.html import format_html
z importu django.core.urlresolvers zpět
třída AccountAdmin (admin.ModelAdmin):
    ...
    def get_urls (self):
        urls = super (). get_urls ()
        custom_urls = [
            url (
                r '^ (? P . +) / vklad / $',
                self.admin_site.admin_view (self.process_deposit),
                name = 'vklad na účet',
            ),
            url (
                r '^ (? P <účet_účtu. +) / výběr / $',
                self.admin_site.admin_view (self.process_withdraw),
                name = 'výběr účtu',
            ),
        ]
        návrat custom_urls + urls
    def account_actions (self, obj):
        návrat format_html (
            ' Vklad  & nbsp;'
            ' Vybrat ',
            reverse ('admin: account-Deposit', args = [obj.pk]),
            reverse ('admin: account-selection', args = [obj.pk]),
        )
    account_actions.short_description = 'Akce účtu'
    account_actions.allow_tags = True

Vykreslíme dvě tlačítka, z nichž každé je spojeno s pohledem, který provádí odpovídající funkci process_deposit / pull. Oba pohledy vykreslí mezilehlou stránku s příslušným formulářem. Po odeslání formuláře pohled přesměruje zpět na stránku s podrobnostmi nebo informuje uživatele o chybě.

Příjemnou funkcí použití pole account_actions je, že je k dispozici v zobrazení podrobností i seznamu, protože se jedná o běžné pole v administrátorovi.

Funkce, která zpracovává skutečnou akci:

# admin.py
z django.http importujte HttpResponseRedirect
z django.template.response import TemplateResponse
z .forms import DepositForm, WithdrawForm
třída AccountAdmin (admin.ModelAdmin):
   ...
   def process_deposit (self, request, account_id, * args, ** kwargs):
        návrat self.process_action (
            request = request,
            account_id = account_id,
            action_form = DepositForm,
            action_title = 'Vklad',
        )
   def process_withdraw (self, request, account_id, * args, ** kwargs):
        návrat self.process_action (
            request = request,
            account_id = account_id,
            action_form = WithdrawForm,
            action_title = 'Výběr',
        )
     
   def process_action (
        já,
        žádost,
        Číslo účtu,
        action_form,
        action_title
   ):
        account = self.get_object (request, account_id)
        if request.method! = 'POST':
            form = action_form ()
        jiný:
            form = action_form (request.POST)
            if form.is_valid ():
                Snaž se:
                    form.save (účet, request.user)
                kromě chyb.Orr jako e:
                    # Pokud je uloženo save (), bude mít formulář zápornou hodnotu
                    # chyba pole obsahující informativní zprávu.
                    složit
                jiný:
                    self.message_user (žádost, 'Úspěch')
                    url = obráceně (
                        'admin: account_account_change',
                       args = [account.pk],
                        current_app = self.admin_site.name,
                    )
                    návrat HttpResponseRedirect (url)
        context = self.admin_site.each_context (žádost)
        context ['opts'] = self.model._meta
        context ['form'] = form
        context ['account'] = účet
        context ['title'] = action_title
        návrat TemplateResponse (
            žádost,
            'admin / account / account_action.html',
            kontext,
        )

Napsali jsme funkci nazvanou process_action, která přijímá formulář, název akce a id účtu a zpracovává odeslání formuláře. Dvě funkce, process_withdraw a process_deposit, se používají k nastavení relevantního kontextu pro každou operaci.

Existuje zde několik kontrolních panelů Django admin, které vyžaduje web správce Django. Nemá smysl kopat do toho příliš hluboko, protože to pro nás v tomto bodě není relevantní.

Zbývá pouze přidat šablonu mezilehlé stránky obsahující formulář. Opět není třeba pracovat příliš tvrdě - Django již má šablonu stránky s podrobnostmi, kterou můžeme rozšířit:

# templates / admin / account / account_action.html
{% rozšiřuje "admin / change_form.html"%}
{% load i18n admin_static admin_modify%}
{% block content%}
  
    {% csrf_token%}
    {% if form.non_field_errors | length> 0%}
      

          "Prosím opravte chyby níže."       

      {{form.non_field_errors}}     {% endif%}
    
      {% pro pole ve tvaru%}         
          {{field.errors}}           {{field.label_tag}}           {{field}}           {% if field.field.help_text%}           

            {{field.field.help_text | safe}}           

          {% endif%}         
      {% endfor%}     
    
           
  
{% endblock%}

To je ono!

Zaměstnanci nyní mohou snadno uložit a vybrat přímo z administračního rozhraní. Není třeba vytvářet drahý řídicí panel nebo ssh na server.

Slíbil jsem, že to uděláme ve 100 řádcích a udělali jsme to za méně!

Zisk!

Úvěry

Velké části implementace jsou převzaty z vynikajícího (vynikajícího!) Balíčku django-import-export. Ušetřilo nám to hodiny „Můžete mi jen poslat data v Excelu?“ A milujeme je za to. Pokud s tím nejste obeznámeni, měli byste si ji určitě prohlédnout.

Kde to můžeme vzít odtud

Nyní, když jsme tuto techniku ​​přibili, můžeme s ní do značné míry dělat, co chceme - máme úplnou kontrolu nad cestou a tím, co se vykresluje. Je také možné přidat tlačítka pro akci, která nevyžadují další data bez mezilehlé stránky.

Dalším krokem by bylo abstrakt této funkce a vložení mixin, ale když se tam dostaneme, překročíme tento most.