如何通过DRF的Django图表--分步骤教程

334 阅读8分钟

Hello Coders!

Django 是一个用Python构建的强大网络框架,你可以用它来创建功能齐全的网络应用程序或网络API。本文介绍了 ,如何使用step-by-step **Django、Django REST框架和Chart.js**展示三种不同的图表( 、 、 图表)。内容介绍了所有层次和概念(API、JS、Html),还提供了 的链接。pie line bar source code, saved on Github

谢谢你的阅读!内容由App Generator 提供。

Django Charts via DRF and Chart.js - Cover Django.

✨ 设置项目

在去之前,请确保,首先,让我们来设置项目。请随意使用你最喜欢的python环境管理工具。我在这里会使用virtualenv

$ virtualenv env
$ source env/bin/activate

之后,我们安装开发中要用到的库,并创建项目。

$ pip install django 
$ pip install djangorestframework 
$ pip install djangorestframework-simplejwt 
$ pip install django-import-export

创建项目

$ django-admin startproject core

我们首先要创建一个应用程序,它将包含所有项目的具体功能。

$ django-admin startapp apps

创建后,删除所有文件和文件夹,除了__init__.pyviews.pymodels.pyapps.py 。然后打开包含Django配置的设置文件,在INSTALLED_APPS 中加入core。

   # core/settings.py
    ...
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'apps'

很好!通过运行下面的命令,确保项目已经设置好了。

$ python manage.py makemigrations
$ python manage.py migrate

之后,运行下面的命令来启动服务器。

$ python manage runserver

如果你访问localhost:8000 ,我们应该看到Django默认的闪屏。

Django Charts via DRF - Default Django Screen

✨ 编写模型

这个项目将包含两个模型。

  • 用户模型。
  • 和产品模型。

按照这个图,我们注意到这些模型有相同的字段,即创建和更新。在为每个功能(用户和产品)创建应用程序之前,让我们创建一个基础模型,它将被用户模型和Product 模型使用。

在apps/models.py中,添加以下内容。

from django.db import models
from django.utils import timezone

class BaseModel(models.Model):
   created = models.DateTimeField(default=timezone.now)
   updated = models.DateTimeField(auto_now=True)

   class Meta:
       abstract = True

abstract=True ,确保不会为这个模型产生迁移。让我们转而编写User 模型。

👉用户模型

在编写模型之前,我们需要创建一个Django应用程序。在apps目录下,输入以下命令。

$ django-admin startapp user

完成后,重写apps/user/apps.py 文件。

from django.apps import AppConfig

class UserConfig(AppConfig):
   default_auto_field = 'django.db.models.BigAutoField'
   name = 'apps.user'
   label = 'apps_user'

而我们现在可以在settings.py 文件中注册应用程序。

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',

   'apps',                       # <-- OLD    
   'apps.user',                  # <-- NEW    

   'import_export',
   'rest_framework'
] 

现在让我们基于BaseModel类来编写用户模型。

from django.contrib.auth.models import AbstractUser
from django.db import models

from apps.models import BaseModel  

class User(AbstractUser, BaseModel): 

   has_purchased = models.BooleanField(default=False)

Django提供了一个名为AbstractUser的类,它带有以下字段。

username = models.CharField(
       _("username"),
       max_length=150,
       unique=True,
       help_text=_(
           "Required. 150 characters or fewer."
       ),
       validators=[username_validator],
       error_messages={
           "unique": _("A user with that username already exists."),
       },
   )
   first_name = models.CharField(_("first name"), max_length=150, blank=True)
   last_name = models.CharField(_("last name"), max_length=150, blank=True)
   email = models.EmailField(_("email address"), blank=True)
   is_staff = models.BooleanField(
       _("staff status"),
       default=False,
       help_text=_("Designates whether the user can log into this admin site."),
   )
   is_active = models.BooleanField(
       _("active"),
       default=True,
       help_text=_(
           "Designates whether this user should be treated as active. "
       ),
   )
   date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

它对我们这里的需求非常有效。我们还将添加一个新的字段has_purchased ,以追踪在API上至少购买过一次的用户。在运行迁移和提交对数据库的修改之前,让我们在settings.py中为Django将使用的AUTH_USER_MODEL ,添加一个配置。

...
AUTH_USER_MODEL = "apps_user.User"
...

现在运行下面的命令。

$ python manage.py makemigrations
$ python manage.py migrate

添加了用户模型后,让我们写一个自定义的管理类,允许用文件导入数据。django-import-export 是一个Django应用程序和库,用于导入和导出数据,包括管理集成。

在apps/user/admin.py中,添加以下内容。

from django.contrib import admin
from import_export import resources
from import_export.admin import ImportMixin

from apps.user.models import User

class UserResource(resources.ModelResource):
   class Meta:
       model = User
       fields = ('id', 'username', 'email', 'has_purchased', 'created', 'updated')

@admin.register(User)
class UserAdmin(ImportMixin, admin.ModelAdmin):
   resource_class = UserResource
   list_display = ('id', 'username', 'email', 'has_purchased', 'created', 'updated')
   list_filter = ('has_purchased',)
   search_fields = ('username', 'email')

一个资源定义了对象如何被映射到它们的导入和导出表示,并处理导入和导出的数据。在这个文件中,我们声明UserResource类,其中包含模型和数据导入时需要的字段。

UserAdmin类也需要显示的字段,用于过滤数据的字段,以及用于搜索的字段。

👉产品模型

在编写Product 模型之前,我们先创建一个名为product的Django 应用程序。

$ django-admin startapp product 

应用程序创建后,修改product目录下的apps.py 文件。

from django.apps import AppConfig  

class ProductConfig(AppConfig):
   default_auto_field = 'django.db.models.BigAutoField'
   name = 'apps.product'
   label = 'apps_product' 

然后,让我们在settings.py文件中注册该应用程序。

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',

   'apps',
   'apps.product',               # <--- NEW 
   'apps.user',

   'import_export',
   'rest_framework'
] 

我们现在可以编写产品模型了。

from django.db import models

from apps.models import BaseModel

class Product(BaseModel):
   name = models.CharField(max_length=255)
   description = models.TextField()
   price = models.DecimalField(max_digits=10, decimal_places=2)
   quantity_sold = models.IntegerField(default=0)

   def __str__(self):
       return self.name

我们还将为产品模型写一个自定义的管理类。在apps/product/admin.py中,添加以下内容。

from django.contrib import admin
from import_export import resources
from import_export.admin import ImportMixin

from apps.product.models import Product

class ProductResource(resources.ModelResource):
   class Meta:
       model = Product
       fields = ('id', 'name', 'price', 'description', 'created')

@admin.register(Product)
class ProductAdmin(ImportMixin, admin.ModelAdmin):
   resource_class = ProductResource
   list_display = ('id', 'name', 'price', 'description', 'created', 'updated')
   list_filter = ('created',)
   search_fields = ('name',)

好了,产品管理和用户管理都写好了,我们现在可以动用管理部分来加载数据库中的样本数据(导入/导出功能)。

✨ 加载样本数据

Django提供了一个管理仪表盘,你可以用它来管理我们Django项目的资源(模型、用户、组)。但是在访问仪表板之前,你需要管理员的凭证。

在shell中,输入以下命令来创建一个新的超级用户。

$ django-admin createsuperuser

一旦超级用户被创建,我们就可以访问我们项目的管理部分localhost:admin/ 。在你成功登录后,你会看到一个类似的页面。

Django Charts via DRF - Admin Section

让我们先为用户上传数据。点击 "添加",你会被转到以下页面。

Django Charts via DRF - Load User's Data.

你通常会看到第一个用户,就是你刚刚创建的超级用户。很好,现在点击导入。你将不得不导入一个CSV文件。你可以在这里找到这个文件的一个例子。

一旦该文件被导入,点击Confirm Import

Django Charts via DRF - Confirm Import Data

你的数据库现在已经充满了一些用户的数据🚀。下一步是使用同样的导入功能在产品上加载产品数据,使用样本数据。

✨ 编写序列化器

序列化器允许我们转换Django数据结构,如查询集或模型实例在Python本地对象,可以很容易地转换为JSON/XML格式。让我们先为User模型编写一个序列化器。

👉用户序列化器

在apps/user里面,创建一个叫做serializers.py的文件。这个文件将包含一个名为UserSerializer 的类。

from rest_framework import serializers

from apps.user.models import User

class UserSerializer(serializers.ModelSerializer):

   class Meta:
       model = User
       fields = ('id', 'username', 'email', 'has_purchased')

ModelSerializer 可以帮助编写基于模型的序列化器,非常容易。它自动匹配字段和它们的类型,甚至添加一些自动验证。

👉产品序列化器

apps/product/ 目录内,创建一个名为serializers.py 的文件。这个文件将包含一个名为ProductSerializer的模型序列化器。

from rest_framework import serializers

from apps.product.models import Product  

class ProductSerializer(serializers.ModelSerializer):
   class Meta:
       model = Product
       fields = ('id', 'name', 'price', 'description', 'created', 'updated')

很好!现在我们有了序列化器,我们可以编写视图集了。

✨ 编写视图集

视图集是一个基于类的视图,能够处理所有的基本HTTP请求。GET, POST, PUT, DELETE,不需要硬编码任何逻辑。我们在这里使用视图集来轻松配置API的路由。

/user/ 视图集应该允许以下结构。

Django Charts via DRF - Users Viewsets

而产品资源将有以下的结构。

Django Charts via DRF - Product Viewsets

👉编写用户视图集

apps/user/ 目录内,创建一个名为viewsets.py 的文件。该文件将包含一个名为UserViewSet的类。

import calendar

from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action

from apps.user.models import User
from apps.user.serializers import UserSerializer

class UserViewSet(viewsets.ModelViewSet):
   http_method_names = ['get']
   queryset = User.objects.all()
   serializer_class = UserSerializer

   @action(detail=False, methods=['get'], url_path='stats/purchased')
   def purchase_stats(self, request, *args, **kwargs):
       users = User.objects.filter(has_purchased=True)
       return Response({
           'total_users': User.objects.count(),
           'total_purchased': users.count(),
           'purchased_percentage': round(users.count() / User.objects.count() * 100, 2)
       }, status=status.HTTP_200_OK)


   @action(detail=False, methods=['get'], url_path='stats/users_created')
   def user_created_per_month(self, request, *args, **kwargs):
       users = User.objects.all()
       months = []
       for user in users:
           months.append(user.created.date().month)
       months = list(set(months))
       months.sort()
       data = []
       for month in months:
           data.append({
               'month': calendar.month_name[month],
               'count': User.objects.filter(created__month=month).count()
           })
       return Response(data, status=status.HTTP_200_OK)

在这个viewset ,我们只允许GET 请求。而且我们还为视图集添加了自定义动作,如purchase_statsuser_created_per_month 。这些动作会计算并返回一些有用的统计信息。

让我们为ProductViewSet做同样的事情。

👉编写产品视图集

apps/product 目录内,创建一个名为viewsets.py的文件。这个文件将包含ProductViewSet 类。

import calendar

from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action

from apps.product.models import Product
from apps.product.serializers import ProductSerializer


class ProductViewSet(viewsets.ModelViewSet):
   http_method_names = ['get']
   queryset = Product.objects.all()
   serializer_class = ProductSerializer

   @action(detail=False, methods=['get'], url_path='sales')
   def sales_over_month(self, request, *args, **kwargs):
       products = Product.objects.all()
       months = []
       for product in products:
           months.append(product.created.date().month)
       months = list(set(months))
       months.sort()
       data = []
       for month in months:
           data.append({
               'month': calendar.month_name[month],
               'count': Product.objects.filter(created__month=month).count()
           })
       return Response(data, status=status.HTTP_200_OK)

很好!视图集准备好了,我们可以注册这些视图集来创建端点并开始提出请求。

✨ 编写API端点

由于我们正在使用viewsets ,我们可以使用路由器自动注册API端点。

在apps目录下,创建一个名为routers.py的文件。

from rest_framework import routers

from apps.user.viewsets import UserViewSet
from apps.product.viewsets import ProductViewSet

router = routers.SimpleRouter()  

router.register('user', UserViewSet)
router.register('product', ProductViewSet)

urlpatterns = [
   *router.urls,
]

然后我们需要在项目的urls.py文件中注册这个文件。然后,这些端点就可以通过浏览器访问了。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
   path('admin/', admin.site.urls),
   path("api/", include(("apps.routers", "api"), namespace="api")),
] 

很好!之后,确保服务器正在运行,因为我们将开始进行一些请求。

✨ 集成Chart.JS

Chart.js is a JavaScript library that makes working and building charts easier.在这个项目中,我们将使用这个库来显示线图、棒图和饼图。

图表上显示的数据将来自我们刚刚建立的REST API,我们将使用Axios从服务器上获取数据。

但首先,让我们为Django模板系统和静态文件做一些配置,如JavaScript文件,它将包含数据获取和图表配置的逻辑。

👉配置

首先,确保你在settings.py文件中为TEMPLATES做了如下配置。

TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [os.path.join(BASE_DIR, 'apps/templates')],
       'APP_DIRS': True,
       'OPTIONS': {
           'context_processors': [
               'django.template.context_processors.debug',
               'django.template.context_processors.request',
               'django.contrib.auth.context_processors.auth',
               'django.contrib.messages.context_processors.messages',
           ],
       },
   },
] 

模板将在apps/template目录下创建。另外,我们需要对静态文件进行如下配置。

STATIC_URL = 'static/'

STATICFILES_DIRS = [
   os.path.join(BASE_DIR, 'static'),
]

👉Index.html - 产品主页

apps/templates ,创建一个名为index.html 的文件。我们只需为bootstrapaxioschartjs 添加基本的导入。

<html lang="en"> 
<head>
<meta charset="utf-8"> 

<!-- Chart JS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">

<!-- Chart JS -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<title>Stats</title>
</head>
<body> 

<h1 class="text-center">Product Stats</h1>

<div class="row">

    <!-- LINE Chart -->
    <div class="col-6 mr-2">
        <h2 class="text-center">
            User created over months
        </h2>
        <canvas id="user-created"></canvas>
     </div>

    <!-- Bar Chart -->
    <div class="col-6 ml-2">
        <h2 class="text-center">
            Product sales over months
        </h2>
        <canvas id="product-sales"></canvas>
    </div>

    <!-- PIE Chart -->    
    <div class="col-4 mx-auto">
        <h2 class="text-center">
            Percentage of users who bought a product
        </h2>
        <canvas id="user-purchased"></canvas>
     </div> 

</div>

一旦我们有了一个最基本的HTML文件,我们需要编写JS代码,消耗API并将数据注入到图表中。

👉Index.JS - Charts and API

在项目的根目录下,创建一个名为static的目录。在这个目录中,创建另一个叫做js的目录。 这个目录将包含静态的JavaScript文件。最后,在新创建的目录中创建一个index.js文件。

让我们来写写获取和配置图表所需的步骤。

  • 添加一个函数来获取数据并返回一个响应
  • 从每个端点获取数据
  • 用标签初始化数据
  • 初始化图表配置
  • 最后,在DOM中创建一个图表。

让我们分解每一步,并将必要的代码可视化,以正确呈现我们美丽的图表。

1# - 添加一个函数来获取数据并返回一个响应

async function getData(url) {
   const res = await axios.get(url);

   return await res.data;

2# - 从每个端点检索数据

let userPurchased = {};
let productSales = [];
let userCreated = [];

// Fetching data for users that have purchased products

userPurchased = getData('/api/user/stats/purchased');

// Fetching data for users created over month

userCreated = getData('/api/user/stats/users_created');

// Fetching data for products sold over month

productSales = getData('/api/product/sales');

3# - 将数据注入图表中

让我们用标签来初始化数据,初始化图表配置,最后在DOM中为userCreate ,创建一个图表。

userCreated.then((response) => {
   const dataUserCreated = {
       labels: response.map((data) => data.month),
       datasets: [{
           label: 'Users that have purchased products',
           backgroundColor: 'rgb(255, 99, 132)',
           borderColor: 'rgb(255, 99, 132)',
           data: response.map((data) => data.count),
       }]
   };

   const configUserCreated = {
       type: 'line',
       data: dataUserCreated,
       options: {}
   };

   // Creating new chart

   new Chart(
       document.getElementById('user-created'),
       configUserCreated
   );
}) 

让我们为userPurchasedproductSales 做同样的事情。

productSales.then((response) => {
   const dataProductSales = {
       labels: response.map((data) => data.month),
       datasets: [{
           label: 'Products sold',
           data: response.map((data) => data.count),
           borderWidth: 1,
           backgroundColor: 'rgb(255, 99, 132)',
       }]
   };

   const configProductSales = {
       type: 'bar',
       data: dataProductSales,
       options: {
           scales: {
               y: {
                   beginAtZero: true
               }
           }
       },
   };

   new Chart(
       document.getElementById('product-sales'),
       configProductSales
   );
});
userPurchased.then((response) => {
   const dataUserPurchased = {
       labels: [
           'Total Users',
           'Total Purchased',
           'Purchased percentage'
       ],
       datasets: [{
           label: 'Users created',
           data: Object.values(response),
           backgroundColor: [
               'rgb(255, 99, 132)',
               'rgb(54, 162, 235)',
               'rgb(255, 205, 86)'
           ],
           hoverOffset: 4
       }]
   };


   const configUserPurchased = {
       type: 'pie',
       data: dataUserPurchased,
   };


   new Chart(
       document.getElementById('user-purchased'),
       configUserPurchased
   );
});

一旦JS部分完全编码完成,最后一步就是更新产品路由并开始使用该项目。

apps directory ,创建一个views.py 文件,并添加以下内容。

from django.template import loader
from django.shortcuts import render
def index(request):
   context = {"segment": "index"}

   return render(request, "index.html", context) 

并将urls.py 添加到apps目录中,内容如下。

from django.urls import path

from . import views

urlpatterns = [
   # The home page
   path("", views.index, name="home"),
] 

并在项目的urls.py 文件中注册apps/urls.py

..
urlpatterns = [
   path('admin/', admin.site.urls),
   path('', include('apps.urls')),
   path("api/", include(("apps.routers", "api"), namespace="api")),
]
.. 

确保服务器正在运行,然后在浏览器中访问该应用程序。这时,我们应该看到三个图表在主页上漂亮地呈现出来。

Django Charts via DRF and Charts.JS: Bar, Line, and Pie Charts.

✨ 结语

在这篇文章中,我们已经学会了使用DjangoDjango REST Framework (DRF)建立一个提供统计数据的API。我们还创建了一个HTML模板来显示由REST API提供的数据,使用Chart.jsAxios

Thanks for reading! 如需更多资源,请随时访问。