Jak z Django Admin udělat lehký přístrojový panel

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

Django Admin je výkonný nástroj pro správu dat v aplikaci. Nebyl však navržen se souhrnnými tabulkami a grafy. Naštěstí nám vývojáři Django Admin usnadnili přizpůsobení.

Chystáme proměnit Django Admin na řídicí panel přidáním grafu a souhrnné tabulky.

Na konci to bude vypadat:

Proč bych to chtěl udělat?

Existuje spousta nástrojů, aplikací a balíčků, které mohou produkovat velmi pěkně vypadající dashboardy. Osobně jsem zjistil, že pokud produkt není skutečným řídicím panelem, většinu času potřebujete jednoduchou souhrnnou tabulku a několik grafů.

Za druhé a stejně důležité - žádné závislosti.

Pokud vše, co potřebujete, je trochu posílit vaše administrátorské rozhraní, tento přístup rozhodně stojí za zvážení.

Založit

Budeme používat hotový model prodeje.

Abychom využili plnou sílu Django Admin, postavíme náš dashboard na zabudovaný ModelAdmin.

K tomu potřebujeme model:

# models.py
třída SaleSummary (Sale):
    třída Meta:
        proxy = True
        verbose_name = 'Shrnutí prodeje'
        verbose_name_plural = 'Shrnutí prodeje'

Model proxy rozšiřuje funkčnost jiného modelu bez vytvoření skutečné tabulky v databázi.

Nyní, když máme model, můžeme vytvořit ModelAdmin:

# admin.py
ze souboru django.contrib import admin
z .models import SaleSummary
@ admin.register (SaleSummary)
třída SaleSummaryAdmin (ModelAdmin):
    change_list_template = 'admin / sale_summary_change_list.html'
    date_hierarchy = 'created'

Protože používáme standardní ModelAdmin, můžeme používat jeho funkce. V tomto příkladu jsem přidal date_hierarchy pro filtrování prodeje podle data vytvoření. Budeme to používat později pro graf.

Aby stránka vypadala jako „běžná“ administrátorská stránka, rozšiřujeme Djangovu šablonu change_list a umístíme náš obsah do bloku result_list:

# sales / templates / admin / sale_summary_change_list.html
{% rozšiřuje „admin / change_list.html“%}
{% block content_title%}
    

Souhrn prodeje

{% endblock%}
{% block result_list%}
    Náš obsah jde sem ...
{% endblock%}
{% block pagination%} {% endblock%}

Takto vypadá naše stránka v tomto okamžiku:

Přidání souhrnné tabulky

Kontext odeslaný do šablony vyplní ModelAdmin ve funkci nazvané changelist_view.

Pro vykreslení tabulky v šabloně načteme data do changelist_view a přidáme je do kontextu:

# admin.py
třída SaleSummaryAdmin (ModelAdmin):
    
    …
    def changelist_view (self, request, extra_context = None):
        response = super () .changeelist_view (
            žádost,
            extra_context = extra_context,
        )
        Snaž se:
            qs = response.context_data ['cl' '. queryset
        kromě (AttributeError, KeyError):
            návratová odpověď
        
        metrics = {
            'Total': Count ('id'),
            „Total_sales“: Sum („cena“),
        }
        response.context_data [‘shrnutí’] = list (
            qs
            .values ​​('sale__category__name')
            .annotate (** metrics)
            .order_by (‘- total_sales ')
        )
        
        návratová odpověď

Pojďme to rozebrat:

  1. Zavolejte super a nechte Djanga dělat svou věc (vyplňte záhlaví, strouhanku, queryset, filtry atd.).
  2. Rozbalte Queryset vytvořený pro nás z kontextu. V tomto okamžiku je dotaz filtrován pomocí všech vložených filtrů nebo hierarchie dat vybraných uživatelem.
  3. Pokud nedokážeme načíst sadu dotazů z kontextu, je to pravděpodobně kvůli neplatným parametrům dotazu. V takových případech bude Django přesměrován, takže nebudeme zasahovat a vracet odpověď.
  4. Agregujte celkový prodej podle kategorie a vraťte seznam (diktát „metriky“ bude vyjasněn v další části).

Nyní, když máme data v kontextu, můžeme je vykreslit v šabloně:

# sale_summary_change_list.html
{% load humanize%}
...
{% block result_list%}
                                                                         
    
      {% pro řádek v souhrnu%}       >                                                  {% endfor%}             
          
             Kategorie           
        
          
             Celkem           
        
          
             Celkový prodej           
        
                   
{{row.sale__category__name}} {{row.total | intcomma}} {{row.total_sales | výchozí: 0 | intcomma}} $                      {{row.total_sales |               výchozí: 0 |               porovf: summary_total.total_sales}}                    
...
{% endblock%}

Značení je důležité - pro získání nativního vzhledu Django musíme vykreslit tabulky stejným způsobem, jakým je vykresluje Django.

To je to, co zatím máme:

Souhrnná tabulka není moc bez spodního řádku. Můžeme použít metriky a udělat nějaký Django ORM voodoo pro rychlý výpočet spodního řádku:

# admin.py
třída SaleSummaryAdmin (ModelAdmin):
    ...
    
    def changelist_view (self, request, extra_context = None):
        ...
        response.context_data [‘summary_total’] = dict (
            qs.aggregate (** metrics)
        )
        návratová odpověď

To je docela cool trik ...

Umožňuje přidat spodní řádek do tabulky:

             ...
        
                                                            
    
Celkem {{summary_total.total | intcomma}} {{summary_total.total_sales | výchozí: 0}} $ 100%

Začíná to mít tvar:

Přidávání filtrů

Používáme administrátora „běžného“ modelu, takže filtry jsou již zapečené.

Pojďme filtrovat podle zařízení:

# admin.py
třída SaleSummaryAdmin (ModelAdmin):
    
    ...
    
    list_filter = (
        'přístroj',
    )

A výsledek:

Přidání grafu

Řídicí panel není úplný bez grafu.

Chystáme se přidat sloupcový graf pro zobrazení prodejů v čase.

K vytvoření našeho grafu použijeme obyčejný HTML a nějaký dobrý CSS s flexboxem. Data pro graf budou časové řady procent, které se použijí jako výška sloupce.

Zpět na náš changelist_view:

# admin.py
z django.db.models.functions import Trunc
ze souboru django.db.models importovat DateTimeField
třída SalesSummaryAdmin (ModelAdmin):
    ...
    def changelist_view (self, request, extra_context = None):
        ...
        summary_over_time = qs.annotate (
            period = Trunc (
                „vytvořeno“,
                'den',
                output_field = DateTimeField (),
            ),
        ) .values ​​('period')
        .annotate (celkem = součet ('cena'))
        .order_by ('period')
        summary_range = summary_over_time.aggregate (
            minimum = Min ('celkem'),
            high = Max ('total'),
        )
        high = summary_range.get ('high', 0)
        low = summary_range.get ('low', 0)
        response.context_data ['summary_over_time'] = [{
            'period': x ['period'],
            'celkem': x ['celkem'] nebo 0,
            'pct': \
               ((x ['total'] nebo 0) - low) / (high - low) * 100
               pokud vysoká> nízká jinde 0,
        } pro x in summary_over_time]
návratová odpověď

Přidáme sloupcový graf do šablony a trochu jej upravíme:

    ...
    

Prodej v čase

    
    
        
        {% for x in summary_over_time%}             
                
                    {{x.total | výchozí: 0 | intcomma}}
                    {{x.period | datum: "d / m / Y"}}                 
            
        {% endfor%}         
    
    

Pro ty z vás, kteří nejsou obeznámeni s flexboxem, tento kus CSS znamená „kreslit zdola nahoru, táhnout doleva a přizpůsobit šířku tak, aby se vešly“.

Takto to vypadá teď:

Vypadá to docela dobře, ale…

Každý pruh v grafu představuje den. Co se stane, když se pokusíme zobrazit data za jeden den? Nebo několik let?

Takový graf je nečitelný a nebezpečný. Načtení tolika dat zaplaví server a vytvoří obrovský soubor HTML.

Django Admin má hierarchii data - uvidíme, jestli to můžeme použít k úpravě období pruhů na základě vybrané hierarchie data:

def get_next_in_date_hierarchy (žádost, date_hierarchy):
    pokud date_hierarchy + '__day' v request.GET:
        návrat 'hodina'
    pokud date_hierarchy + '__měsíc' v request.GET:
        návrat 'den'
    pokud date_hierarchy + '__year' v request.GET:
        návrat 'týden'
    návrat 'měsíc'
  • Pokud uživatel filtroval jeden den, každý sloupec bude jednu hodinu (maximálně 24 barů).
  • Pokud uživatel vybral měsíc, každý sloupec bude jeden den (maximálně 31 barů).
  • Pokud uživatel vybere rok, bude každý sloupec jeden týden (maximálně 52 barů).
  • Více než to a každý bar bude jeden měsíc.

Nyní potřebujeme pouze jednu malou úpravu zobrazení seznamu změn:

třída SalesSummaryAdmin (ModelAdmin):
    
   ...
    
    def changelist_view (self, request, extra_context = None):
        
       ...
        
        period = get_next_in_date_hierarchy (
            žádost,
            self.date_hierarchie,
        )
        response.context_data ['period' '= period
        summary_over_time = qs.annotate (
            period = Trunc (
                „vytvořeno“,
                doba,
                output_field = DateTimeField (),
            ),
        ) .values ​​('period')
        .annotate (celkem = součet ('cena'))
        .order_by ('period')
        
        ...

Argument období předaný Trunc je nyní parametr.

Výsledek:

To je krásný trend ...

Kde to můžeme vzít odtud?

Nyní, když máte veškerý tento volný čas od nevyvíjení vlastního palubního panelu, můžete:

  • Zrychlete to.
  • Přidejte tlačítko.