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

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

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

Ще превърнем Django Admin в табло за управление, като добавим диаграма и обобщена таблица.

Ето как ще изглежда в края:

Защо бих искал да го направя?

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

Второ и също толкова важно - няма зависимости.

Ако всичко, от което се нуждаете, е малко стимул за вашия администраторски интерфейс, този подход определено си струва да се обмисли.

Настройвам

Ще използваме съставен модел за продажба.

За да използваме пълната мощност на Django Admin, ще базираме таблото си на вградения ModelAdmin.

За целта ни е необходим модел:

# models.py
клас Продажба Обобщение (продажба):
    клас Meta:
        proxy = Вярно
        verbose_name = „Резюме на продажбата“
        verbose_name_plural = 'Обобщение на продажбите'

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

Сега, когато имаме модел, можем да създадем ModelAdmin:

# admin.py
от django.contrib администратор за импортиране
от .модели внос SaleSummary
@ Admin.register (SaleSummary)
клас SaleSummaryAdmin (ModelAdmin):
    change_list_template = 'admin / sale_summary_change_list.html'
    date_hierarchy = 'създаден'

Тъй като използваме стандартен ModelAdmin, можем да използваме неговите функции. В този пример добавих дата_хиерархия, за да филтрирам продажбите по дата на създаване. Ще използваме това по-късно за диаграмата.

За да поддържаме страницата да изглежда като „обикновена“ администраторска страница, ние разширяваме шаблона на Django за промяна_ списък и поставяме съдържанието ни в блока с резултати с резултати:

# sales / templates / admin / sale_summary_change_list.html
{% разширява “admin / change_list.html”%}
{% блокиране на съдържание_title%}
    

Обобщение на продажбите

{% endblock%}
{% блокиране на резултата_ списък%}
    Нашето съдържание отива тук ...
{% endblock%}
{% блокиране на страници%} {% endblock%}

Ето как изглежда нашата страница в този момент:

Добавяне на обобщена таблица

Контекстът, изпратен към шаблона, се попълва от ModelAdmin във функция, наречена changelist_view.

За да изобразим таблицата в шаблона, ние извеждаме данните в changelist_view и ги добавяме в контекста:

# admin.py
клас SaleSummaryAdmin (ModelAdmin):
    
    ...
    def changelist_view (самостоятелно, заявка, extra_context = Няма):
        response = super (). changelist_view (
            поискване,
            extra_context = extra_context,
        )
        опитвам:
            qs = response.context_data ['cl']. queryset
        освен (AttributeError, KeyError):
            отговор за връщане
        
        показатели = {
            „Общо“: Брой („id“),
            „Total_sales“: сума („цена“),
        }
        response.context_data ['резюме'] = списък (
            QS
            .values ​​( "sale__category__name)
            .annotate (** показатели)
            .order_by ( "-") total_sales
        )
        
        отговор за връщане

Нека го разделим:

  1. Обадете се супер, за да оставите Django да свърши работата си (попълнете заглавки, галета, набор от заявки, филтри и така нататък).
  2. Извадете набора от заявки, създаден за нас от контекста. В този момент заявката се филтрира с всякакви вградени филтри или йерархия за дата, избрани от потребителя.
  3. Ако не можем да извлечем набора от заявки от контекста, това е най-вероятно поради невалидни параметри на заявката. В случаи като този Django ще пренасочи, така че ние не се намесваме и връщаме отговора.
  4. Обобщете общите продажби по категории и върнете списък (диктатът „показатели“ ще стане ясен в следващия раздел).

Сега, когато имаме данни в контекста, можем да ги представим в шаблона:

# sale_summary_change_list.html
{% load humanize%}
...
{% блокиране на резултата_ списък%}
    <Маса>              <Врекламния>                <То>           
             Категория                             <То>           
             Общо                             <То>           
             Общи продажби                             <То>           
                           % от общите продажби                                             
    
      {% за резюме на ред%}
      
         {{row.sale__category__name}} 
         {{row.total | intcomma}} 
         {{row.total_sales | по подразбиране: 0 | intcomma}} $ 
        
          <Силно>
          {{row.total_sales |
              по подразбиране: 0 |
              проценти: Summa_total.total_sales}}
          
        
      
      {% endfor%}
    
    
  
...
{% endblock%}

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

Това е, което имаме досега:

Обобщена таблица не е много без долен ред. Можем да използваме показателите и да направим някои вуду на Django ORM за бързо изчисляване на долния ред:

# admin.py
клас SaleSummaryAdmin (ModelAdmin):
    ...
    
    def changelist_view (самостоятелно, заявка, extra_context = Няма):
        ...
        response.context_data ['Summa_total'] = dict (
            qs.aggregate (** показатели)
        )
        отговор за връщане

Това е доста готин трик ...

Да добавим долния ред в таблицата:

    <Маса>         ...
        
             Общо 
             {{Summa_total.total | intcomma}} 
             {{summa_total.total_sales | по подразбиране: 0}} $ 
             100% 
        
    

Това започва да се оформя:

Добавяне на филтри

Ние използваме „обикновен“ администратор на модел, така че филтрите вече са изпечени.

Да филтрираме по устройство:

# admin.py
клас SaleSummaryAdmin (ModelAdmin):
    
    ...
    
    list_filter = (
        "Устройство",
    )

И резултатът:

Добавяне на диаграма

Табло за управление не е пълно без диаграма.

Ще добавим лентова диаграма, за да показваме продажбите във времето.

За да изградим нашата диаграма, ще използваме обикновен HTML код и някои добри ol’s CSS с flexbox. Данните за диаграмата ще представляват времева серия от проценти, които ще се използват като височина на лентата.

Обратно към нашия changelist_view:

# admin.py
от django.db.models.functions импортира Trunc
от django.db.models import DateTimeField
клас SalesSummaryAdmin (ModelAdmin):
    ...
    def changelist_view (самостоятелно, заявка, extra_context = Няма):
        ...
        sum_over_time = qs.annotate (
            период = TRUNC (
                "Създаден",
                "Ден",
                output_field = DateTimeField (),
            ),
        ) .values ​​( "период")
        .annotate (общо = Sum ( "цена"))
        .order_by ( "период")
        Summa_range = Summary_over_time.aggregate (
            ниско = Min ( "обща"),
            високо = Max ( "обща"),
        )
        high = sum_range.get ('високо', 0)
        ниска = sum_range.get ('ниска', 0)
        response.context_data ['sum_over_time'] = [{
            'период': x ['период'],
            'общо': x ['общо'] или 0,
            'pct': \
               ((x ['общо'] или 0) - ниско) / (високо - ниско) * 100
               ако е високо> ниско друго 0,
        } за x in sum_over_time]
отговор за връщане

Нека добавим лентовата диаграма към шаблона и да я стилизираме малко:

    ...
    

Продажби във времето

    <Стил>
    .bar-диаграма {
      дисплей: гъвкав;
      обосновка-съдържание: пространство около;
      височина: 160px;
      подплънки отгоре: 60px;
      преливник: скрит;
    }
    .bar-chart .bar {
        гъвкавост: 100%;
        подравняване: самостоятелен флекс;
        марж-дясно: 2px;
        позиция: относителна;
        цвят на фона: # 79aec8;
    }
    .bar-chart .bar: последно дете {
        марж: 0;
    }
    .bar-chart .bar: задръжте {
        цвят на фона: # 417690;
    }
    .bar-chart .bar .bar-tooltip {
        позиция: относителна;
        z-индекс: 999;
    }
    .bar-chart .bar .bar-tooltip {
        позиция: абсолютна;
        отгоре: -60px;
        вляво: 50%;
        трансформация: translateX (-50%);
        подравняване на текст: център;
        шрифт: удебелен;
        непрозрачност: 0;
    }
    .bar-chart .bar: hover .bar-tooltip {
        непрозрачност: 1;
    }
    
    
    
        
        {% за x в sum_over_time%}             
                
                    {{x.total | по подразбиране: 0 | intcomma}}
                    {{x.period | дата: "г / м / Y"}}                                       {% endfor%}                   

За онези от вас, които не са запознати с flexbox, това парче CSS означава „нарисувайте отдолу нагоре, издърпайте наляво и регулирайте ширината, за да пасне“.

Ето как изглежда сега:

Изглежда доста добре, но ...

Всяка лента в диаграмата представлява ден. Какво ще се случи, когато се опитаме да покажем данни за един ден? Или няколко години?

Графика като тази е едновременно нечетлива и опасна. Извличането на толкова много данни ще залее сървъра и ще генерира огромен HTML файл.

Администраторът на Django има йерархия за дата - нека да видим дали можем да го използваме, за да коригираме периода на баровете въз основа на избраната йерархия за дата:

дефинирай get_next_in_date_hierarchy (заявка, дата_hierarchy):
    ако date_hierarchy + '__day' в заявката.GET:
        връщане "час"
    ако date_hierarchy + '__month' в заявката.GET:
        връщане 'ден'
    ако date_hierarchy + '__year' в заявката.GET:
        връщане „седмица“
    връщане "месец"
  • Ако потребителят филтрира един ден, всеки бар ще бъде един час (максимум 24 бара).
  • Ако потребителят е избрал месец, всеки бар ще бъде един ден (макс. 31 бара).
  • Ако потребителят е избрал година, всеки бар ще бъде една седмица (макс. 52 бара).
  • Повече от това и всеки бар ще бъде един месец.

Сега имаме нужда от само една малка корекция на изгледа на списъка с промени:

клас SalesSummaryAdmin (ModelAdmin):
    
   ...
    
    def changelist_view (самостоятелно, заявка, extra_context = Няма):
        
       ...
        
        период = get_next_in_date_hierarchy (
            поискване,
            self.date_hierarchy,
        )
        response.context_data ['период'] = период
        sum_over_time = qs.annotate (
            период = TRUNC (
                "Създаден",
                Период,
                output_field = DateTimeField (),
            ),
        ) .values ​​( "период")
        .annotate (общо = Sum ( "цена"))
        .order_by ( "период")
        
        ...

Аргументът за период, предаден на Trunc, вече е параметър.

Резултатът:

Това е красива тенденция ...

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

Сега, когато разполагате с цялото това свободно време от това да не търкаляте собственото си табло, можете:

  • Направете го по-бързо.
  • Добавете бутон.