Django admin에 차트 추가

소개


Django는 데이터베이스 관리를 위한 CRUD 인터페이스가 있는 기존 기능 관리 UI를 제공합니다.이것은 기본 내용과 사용자 관리 시스템의 대다수 용례를 포함한다.그러나 요약이나 역사적 추세를 탐색하는 보기가 없습니다. 이것은 관리 대시보드에서 기대하는 것입니다.
다행히도django 관리 프로그램은 확장이 가능합니다. 조금만 조정하면 상호작용식 자바스크립트 도표를 관리 프로그램에 추가할 수 있습니다.

문제


나는 findwork.dev 에서 전자 우편 구독자의 시간에 따라 변화하는 도형 개관을 얻고 싶다.이 사이트는 전자 우편 구독자 방면에서 증가하고 있습니까 아니면 정체되어 있습니까?지난달에 우리는 몇 명의 사용자가 있었습니까?우리는 어느 주에 가장 많은 구독자를 얻었습니까?모든 구독자가 그들의 이메일을 검증하고 있습니까?
탐색적 도표를 사용하면 우리는 우리 사이트의 역사적 표현을 이해할 수 있다.
내가 최초로 탐색한 것은 기존의 Django 관리 응용 프로그램과 계기판이었다.그 요구는 도표를 그리는 능력을 포함하고 좋은 기록을 가지고 있어 보기 좋다는 것이다.내가 실험한 모든 응용 프로그램은 스타일 면에서 기본 관리자보다 낫지만, 문서가 부족하거나 유지 보수가 없다.

  • xadmin - 영어 문서 없음

  • django-jet - 부터
    핵심 팀이 개발 중SaaS alternative

  • django-grapinelli - 아니요
    도면 제작 능력
  • 이때 머릿속에 왜 기본 관리 프로그램을 확장하지 않습니까?

    확장django admin


    django admin 응용 프로그램은 ModelAdmin classes 으로 구성되어 있습니다.이러한 표현은 관리 인터페이스에서 모델의 시각적 뷰를 나타냅니다.기본적으로 ModelAdmin 클래스에는 5개의 기본 뷰가 있습니다.
  • 변경 목록 - 모델 컬렉션의 목록 보기
  • 추가 - 새 모델 인스턴스를 추가할 수 있는 뷰
  • 변경 사항 - 모델 인스턴스를 업데이트하는 뷰
  • 삭제 - 모델 인스턴스 삭제를 확인하는 뷰
  • 역사 - 모델 실례에 대한 조작 역사
  • 특정 모델을 보려면 변경 목록 보기가 기본 관리 보기입니다.이메일 Subscribers 페이지를 열 때마다 시간의 흐름에 따라 추가된 구독자를 볼 수 있도록 도표를 추가하고 싶습니다.
    e-메일 구독자 모델이 있다고 가정합니다.
    # web/models.py
    from django.db import models
    
    class EmailSubscriber(models.Model):
        email = models.EmailField()
        created_at = models.DateTimeField()
    
    관리 응용 프로그램에 전자 우편 구독자를 표시하기 위해서는 django.contrib.admin.ModelAdmin 에서 확장된 클래스를 만들어야 합니다.
    기본 ModelAdmin은 다음과 같이 보입니다.
    # web/admin.py
    from django.contrib import admin
    from .models import EmailSubscriber
    
    @admin.register(EmailSubscriber)
    class EmailSubscriberAdmin(admin.ModelAdmin):
        list_display = ("id", "email", "created_at") # display these table columns in the list view
        ordering = ("-created_at",)                  # sort by most recent subscriber
    
    구독자를 추가하면 다음과 같은 초기 데이터 세트가 제공됩니다.
    $ ./manage.py shell
    Python 3.7.3 (default, Apr  9 2019, 04:56:51)
    [GCC 8.3.0] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    (InteractiveConsole)
    from web.models import EmailSubscriber
    from django.utils import timezone
    from datetime import timedelta
    import random
    for i in range(0, 100):
        EmailSubscriber.objects.create(email=f"user_{i}@email.com", created_at=timezone.now() - timedelta(days=random.randint(0, 100)))
    ...
    <EmailSubscriber: EmailSubscriber object (1)>
    <EmailSubscriber: EmailSubscriber object (2)>
    <EmailSubscriber: EmailSubscriber object (3)>
    ...
    
    변경 목록 보기에 들어가면 랜덤 생성 시간을 100개 추가한 새 구독자 http://localhost:8000/admin/web/emailsubscriber/ 를 볼 수 있습니다.

    만약에 우리가 도표를 추가하고 싶다면, 이 도표는 한동안 줄무늬 도표의 구독자 수를 정리했다.우리는 그것을 구독자 목록 위에 놓고 싶다. 이렇게 하면 네가 사이트에 들어가자마자 그것을 볼 수 있다.
    아래의 빨간색 구역은 내가 직관적으로 도표를 놓고 싶은 위치를 그려냈다.

    새 파일을 만들면, 기본 템플릿이 아닌django 관리자에게 템플릿을 불러올 수 있습니다.에서 빈 파일을 만듭니다.
  • web/templates/admin/web/emailsubscriber/change_list.html .
  • 관리 템플릿을 덮어쓸 때 명명된 스키마는
  • {{app}}/templates/admin/{{app}}/{{model}}/change_list.html .
  • 기본 변경 목록 보기는 여러 개의 블록을 포함하여 확장할 수 있으며, 이러한 블록을 덮어써서 사용자의 요구를 충족시킬 수 있습니다.검사 the default admin template 를 할 때, 우리는 그것이 다시 쓸 수 있는 블록을 포함하는 것을 볼 수 있다.모델 테이블 이전에 렌더링된 내용을 변경하려면 블록content을 덮어써야 합니다.
    기본 변경 목록 보기를 확장하고 사용자 정의 텍스트를 추가합니다.
    # web/templates/admin/web/emailsubscriber/change_list.html
    
    {% extends "admin/change_list.html" %}
    {% load static %}
    {% block content %}
    
    <h1>Custom message!</h1>
    
    <!-- Render the rest of the ChangeList view by calling block.super -->
    {{ block.super }}
    {% endblock %}
    

    멋지다. 우리는 현재 관리 UI를 성공적으로 맞춤 제작했다.더 나아가 Chart.js 를 사용하여 Javascript 차트를 추가합니다.스크립트와 스타일 요소를load Chart에 추가하려면 extrahead 블록을 덮어써야 합니다.제목에 있는 js.
    도표.js 코드는 그들이 찾은 프레젠테이션 그래프here를 기반으로 한다.나는 X축의 시간 시퀀스 데이터를 읽기 위해 그것을 약간 수정했다.
    # web/templates/admin/web/emailsubscriber/change_list.html
    
    {% extends "admin/change_list.html" %}
    {% load static %}
    
    <!-- Override extrahead to add Chart.js -->
    {% block extrahead %}
    {{ block.super }}
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.css" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.bundle.min.js"></script>
    <script>
    document.addEventListener('DOMContentLoaded', () => {
      const ctx = document.getElementById('myChart').getContext('2d');
    
      // Sample data
      const chartData = [
        {"date": "2019-08-08T00:00:00Z", "y": 3},
        {"date": "2019-08-07T00:00:00Z", "y": 10},
        {"date": "2019-08-06T00:00:00Z", "y": 15},
        {"date": "2019-08-05T00:00:00Z", "y": 4},
        {"date": "2019-08-03T00:00:00Z", "y": 2},
        {"date": "2019-08-04T00:00:00Z", "y": 11},
        {"date": "2019-08-02T00:00:00Z", "y": 3},
        {"date": "2019-08-01T00:00:00Z", "y": 2},
      ];
    
      // Parse the dates to JS
      chartData.forEach((d) => {
        d.x = new Date(d.date);
      });
    
      // Render the chart
      const chart = new Chart(ctx, {
        type: 'bar',
        data: {
          datasets: [
            {
              label: 'new subscribers',
              data: chartData,
              backgroundColor: 'rgba(220,20,20,0.5)',
            },
          ],
        },
        options: {
          responsive: true,
          scales: {
            xAxes: [
              {
                type: 'time',
                time: {
                  unit: 'day',
                  round: 'day',
                  displayFormats: {
                    day: 'MMM D',
                  },
                },
              },
            ],
            yAxes: [
              {
                ticks: {
                  beginAtZero: true,
                },
              },
            ],
          },
        },
      });
    });
    </script>
    {% endblock %}
    
    {% block content %}
    <!-- Render our chart -->
    <div style="width: 80%;">
      <canvas style="margin-bottom: 30px; width: 60%; height: 50%;" id="myChart"></canvas>
    </div>
    <!-- Render the rest of the ChangeList view -->
    {{ block.super }}
    {% endblock %}
    

    봐라, 우리는 지금 이미 도표를 하나 그렸다.js 그래프를 django 관리자로 변환합니다.유일한 문제는 데이터가 백엔드에서 파생된 것이 아니라 하드코딩된 것이다.

    관리 템플릿에 차트 데이터 주입


    ModelAdmin 클래스에는 changelist_view 라는 방법이 있습니다.이 방법은 변경 목록 페이지를 보여주는 것을 책임진다.이 방법을 다시 쓰면 도표 데이터를 템플릿 상하문에 주입할 수 있습니다.
    아래의 코드는 대체적으로 이 점을 실현했다.
  • 하루 간격
  • 의 신규 구독자 총 집합
  • Django 쿼리 세트를 JSON
  • 으로 인코딩
  • 템플릿 컨텍스트에 데이터 추가
  • 슈퍼 () 방법으로 페이지를 표시합니다
  • # django_admin_chart_js/web/admin.py
    import json
    
    from django.contrib import admin
    from django.core.serializers.json import DjangoJSONEncoder
    from django.db.models import Count
    from django.db.models.functions import TruncDay
    
    from .models import EmailSubscriber
    
    
    @admin.register(EmailSubscriber)
    class EmailSubscriberAdmin(admin.ModelAdmin):
        list_display = ("id", "email", "created_at")
        ordering = ("-created_at",)
    
        def changelist_view(self, request, extra_context=None):
            # Aggregate new subscribers per day
            chart_data = (
                EmailSubscriber.objects.annotate(date=TruncDay("created_at"))
                .values("date")
                .annotate(y=Count("id"))
                .order_by("-date")
            )
    
            # Serialize and attach the chart data to the template context
            as_json = json.dumps(list(chart_data), cls=DjangoJSONEncoder)
            extra_context = extra_context or {"chart_data": as_json}
    
            # Call the superclass changelist_view to render the page
            return super().changelist_view(request, extra_context=extra_context)
    
    기술적으로 말하자면, 데이터는 템플릿 상하문에 추가되어야 하지만, 우리는 현재 하드코딩된 데이터가 아니라 도표에서 그것을 사용해야 한다.
    chartData 변수의 하드 인코딩 데이터를 백엔드의 데이터로 대체합니다.
    // django_admin_chart_js/web/templates/admin/web/emailsubscriber/change_list.html
    const chartData = {{ chart_data | safe }};
    
    아름다운 도표를 보기 위해 페이지를 다시 불러옵니다.

    JS를 사용하여 데이터 동적 로드


    위의 예시에서 우리는 초기 도표 데이터를 html 템플릿에 직접 주입할 것이다.초기 페이지를 불러오면 우리는 더욱 상호작용을 가지고 데이터를 얻을 수 있다.이를 위해서는 다음과 같은 작업이 필요합니다.
  • 모델 관리자에게 JSON 데이터
  • 를 반환하는 새 노드를 추가합니다.
  • JS 논리를 추가하고 버튼을 클릭하여 AJAX 호출을 수행하고 차트를 다시 표시
  • 새 노드를 추가하려면 모델admin의 get_urls() 방법을 덮어쓰고 자신의 노드 URL을 주입해야 합니다.
    사용자 정의 URL은 기본 URL 앞에 있어야 합니다.기본값은 허용되며, 모든 내용과 일치하기 때문에 요청은 우리의 사용자 정의 방법을 통과하지 않습니다.
    우리의python 코드는 지금 이렇게 해야 한다.
    # web/admin.py
    import json
    
    from django.contrib import admin
    from django.core.serializers.json import DjangoJSONEncoder
    from django.db.models import Count
    from django.db.models.functions import TruncDay
    from django.http import JsonResponse
    from django.urls import path
    
    from .models import EmailSubscriber
    
    @admin.register(EmailSubscriber)
    class EmailSubscriberAdmin(admin.ModelAdmin):
        list_display = ("id", "email", "created_at")
        ordering = ("-created_at",)
    
        ...
    
        def get_urls(self):
            urls = super().get_urls()
            extra_urls = [
                path("chart_data/", self.admin_site.admin_view(self.chart_data_endpoint))
            ]
            # NOTE! Our custom urls have to go before the default urls, because they
            # default ones match anything.
            return extra_urls + urls
    
        # JSON endpoint for generating chart data that is used for dynamic loading
        # via JS.
        def chart_data_endpoint(self, request):
            chart_data = self.chart_data()
            return JsonResponse(list(chart_data), safe=False)
    
        def chart_data(self):
            return (
                EmailSubscriber.objects.annotate(date=TruncDay("created_at"))
                .values("date")
                .annotate(y=Count("id"))
                .order_by("-date")
            )
    
    단추를 눌렀을 때 도표 데이터를 다시 불러오고 도표를 다시 보이기 위해 Javascript 논리를 추가해야 합니다.차트 변수 선언 아래에 다음 행을 추가합니다.
      // django_admin_chart_js/web/templates/admin/web/emailsubscriber/change_list.html
    
      const chart = new Chart...
      ...
    
      // Reload chart data from the backend on button click
      const btn = document.querySelector('#reload');
      btn.addEventListener('click', async() => {
        const res = await fetch("/admin/web/emailsubscriber/chart_data/");
        const json = await res.json();
        json.forEach((d) => {
          d.x = new Date(d.date);
        });
        chart.data.datasets[0].data = json;
        chart.update();
      });
    
    아래 도표에 html 단추를 추가합니다.
    {% block content %}
    <!-- Render our chart -->
    <div style="width: 80%;">
      <canvas style="margin-bottom: 30px; width: 60%; height: 50%;" id="myChart"></canvas>
    </div>
    
    <button id="reload" style="margin: 1rem 0">Reload chart data</button>
    <!-- Render the rest of the ChangeList view -->
    {{ block.super }}
    {% endblock %}
    

    도표js는 서로 다른 기존의 가시화를 제공했다.기본 도표를 사용하면 쉽고 필요에 따라 맞춤형으로 제작할 수 있습니다.
    도표.js 문서는 here, Django 관리 문서는 here입니다.
    전체 예시 코드는 Github 에서 찾을 수 있습니다.

    좋은 웹페이지 즐겨찾기