用 Django 开发简单的前后端分离应用

Timeline

2024-3-14:create article

2024-3-21:add DRF & Serializer

官方文档

https://docs.djangoproject.com/

使用 Django 开发简单前后端分离应用

课程要求

【Part 1】SaaS 后台框架搭建

  • 结合需求创建model模型
  • 通过DRF提供restful接口,其中标准运维相关接口以mock的形式提供
  • 以装饰器的方式mock权限相关代码

【Part 2】前端页面开发

  • 使用 lesscode 完成任务列表页面
  • 新增部署按钮,刷新按钮,查看任务按钮
  • 新建部署弹框(下拉框,文本输入框)
  • 事件绑定、计算属性、侦听器等
  • 进阶完成 lesscode 暂时无法实现的功能
  • 把所有接口对接至后台接口

【Part 3】APIGateway 接入

  • 讲解APIGateway对应文档查询
  • 把Part3中mock掉的标准运维接口,调整为Gateway调用

【Part 4】异步任务支持

  • 把查询标准运维任务状态的功能,通过celery周期任务实现
  • 完成任务的消息通知,通过celery异步执行

【Part 5】权限中心接入

  • 注册本项目的权限模型到权限中心(任务创建,执行记录查看)
  • 对后台提供的接口结合iam sdk进行鉴权

后台框架搭建

流程:创建项目和应用、定义模型、调用 API 存储数据、创建视图(编写 API 并处理数据)、暴露接口

创建 App:host_query

  • 在manage.py统计目录下创建host-query目录:python manage.py startapp host_query
  • host_query中创建 urls.py
1
2
3
4
5
6
from django.urls import path
from host_query import views

urlpatterns = [
path("", views.index, name="index"),
]
  • 下一步在根URLconf文件中指定我们创建 的 host_query.urls 模块.在 /urls.py 文件的 urlpatterns 列表里插入一个 include()
1
url(r'^host_query/', include('host_query.urls'))

定义 Models:Business、Host

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from django.db import models
# Create your models here.

#Business
class Business(models.Model):
bk_biz_id = models.CharField(verbose_name="业务id", primary_key=True, max_length=255)
bk_biz_name = models.CharField(max_length=255, verbose_name="业务名", blank=True, null=True)
def __str__(self):
return self.bk_biz_name

# Host model
class Host(models.Model):
bk_host_id = models.CharField(verbose_name="主机id", primary_key=True, max_length=255)
bk_cloud_id = models.CharField(verbose_name="云区域", blank=True, max_length=255,null=True)
bk_biz_id = models.CharField(verbose_name="业务id", blank=True, max_length=255,null=True)

bk_os_type = models.CharField(verbose_name="操作系统类型", max_length=255, blank=True, null=True)
bk_host_innerip = models.GenericIPAddressField(verbose_name="内网IP", blank=True, null=True)
bk_mac = models.GenericIPAddressField(verbose_name="内网MAC地址", blank=True, null=True)

bk_set_id = models.CharField(verbose_name="集群ID", max_length=255, blank=True, null=True)
bk_set_name = models.CharField(verbose_name="集群名", max_length=2500, blank=True, null=True)

bk_module_id = models.CharField(verbose_name="模块ID", max_length=255, blank=True, null=True)
bk_module_name = models.CharField(verbose_name="模块名", max_length=255, blank=True, null=True)

def __str__(self):
return self.bk_host_innerip

创建 Tasks:

这部分主要是调用各个 api 进行数据读取,之后接入前端进行渲染

  • 封装 api 请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class api_request:
    def __init__(self, base_url, bk_app_code, bk_app_secret, access_token):
    self.base_url = base_url
    self.bk_app_code = bk_app_code
    self.bk_app_secret = bk_app_secret
    self.access_token = access_token

    def get_headers(self):
    auth_dict = {
    "bk_app_code": self.bk_app_code,
    "bk_app_secret": self.bk_app_secret,
    "access_token": self.access_token,
    }
    return {"X-Bkapi-Authorization": json.dumps(auth_dict)}

    api_request_instance = api_request(
    base_url="<http://api/xxx>",
    bk_app_code=os.environ.get('BKPAAS_APP_ID'),
    bk_app_secret=os.environ.get('BKPAAS_APP_SECRET'),
    access_token='xxx',
    )

    header = api_request_instance.get_headers
  • 调用业务查询接口查询业务并写入数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #Business model
    class Business(models.Model):
    bk_biz_id = models.CharField(verbose_name="业务id", primary_key=True, max_length=255)
    bk_biz_name = models.CharField(max_length=255, verbose_name="业务名", blank=True, null=True)
    def __str__(self):
    return self.bk_biz_name

    # search all business
    def get_business_info() -> list:
    plus_url = "api/xxx/search_business/"
    url = api_request_instance.base_url + plus_url
    response = requests.post(url=url, json={"fields": ["bk_biz_id", "bk_biz_name"]}, headers=header).content
    json_data = json.loads(response.decode("utf-8"))

    return (json_data['data']['info'])

    # add business to database
    @periodic_task(run_every=crontab())
    def add_business_to_database():
    all_business = get_business_info()
    create_businesses = []

    for business in all_business:
    create_businesses.append(
    Business(bk_biz_id=business.get("bk_biz_id"), bk_biz_name=business.get("bk_biz_name"))
    )
    # print(create_businesses)
    with transaction.atomic():
    Business.objects.bulk_create(create_businesses)
  • 按照业务将对应的主机写入数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    # Host model
    class Host(models.Model):
    bk_host_id = models.CharField(verbose_name="主机id", primary_key=True, max_length=255)
    bk_cloud_id = models.CharField(verbose_name="云区域", blank=True, max_length=255,null=True)
    bk_biz_id = models.CharField(verbose_name="业务id", blank=True, max_length=255,null=True)

    bk_os_type = models.CharField(verbose_name="操作系统类型", max_length=255, blank=True, null=True)
    bk_host_innerip = models.GenericIPAddressField(verbose_name="内网IP", blank=True, null=True)
    bk_mac = models.GenericIPAddressField(verbose_name="内网MAC地址", blank=True, null=True)

    bk_set_id = models.CharField(verbose_name="集群ID", max_length=255, blank=True, null=True)
    bk_set_name = models.CharField(verbose_name="集群名", max_length=2500, blank=True, null=True)

    bk_module_id = models.CharField(verbose_name="模块ID", max_length=255, blank=True, null=True)
    bk_module_name = models.CharField(verbose_name="模块名", max_length=255, blank=True, null=True)

    def __str__(self):
    return self.bk_host_innerip

    # get host from business
    def get_host_from_business(business_id, start, limit):
    plus_url = "api/xxx/list_biz_hosts_topo"
    url = api_request_instance.base_url + plus_url
    response = requests.post(
    url=url,
    json={
    "page": {"start": start, "limit": limit},
    "bk_biz_id": f"{business_id}",
    "fields": [
    "bk_host_id",
    "bk_cloud_id",
    "bk_os_type",
    "bk_host_innerip",
    "bk_mac",
    "bk_set_id",
    "bk_set_name",
    "bk_module_id",
    "bk_module_name",
    ],
    },
    headers=header,
    ).content
    json_data = json.loads(response.decode("utf-8"))
    return json_data["data"]["info"], json_data["data"]["count"]

    # add host to database
    @periodic_task(run_every=crontab())
    def add_host_to_database():
    all_business_info = get_business_info()
    for business_info in all_business_info:
    start = 0
    limit = 300
    all_host_info, count = get_host_from_business(business_info.get('bk_biz_id'), start, limit)

    start += limit
    while count > start:
    next_page, count = get_host_from_business(business_info.get('bk_biz_id'), start, limit)
    all_host_info.extend(next_page)
    start += 300

    creating_hosts = []
    bk_host_ids = [host["host"]["bk_host_id"] for host in all_host_info]

    existing_host_ids = set(Host.objects.filter(bk_host_id__in=bk_host_ids).values_list("bk_host_id", flat=True))
    for host_info in all_host_info:

    bk_set_id = host_info["topo"][0]["bk_set_id"]
    bk_set_name = host_info["topo"][0]["bk_set_name"]

    bk_module_id = host_info["topo"][0]["module"][0]["bk_module_id"]
    bk_module_name = host_info["topo"][0]["module"][0]["bk_module_name"]

    host_info_host = host_info["host"]
    creating_hosts_before = Host(
    bk_host_id=host_info_host["bk_host_id"],
    bk_cloud_id=host_info_host["bk_cloud_id"],
    bk_biz_id=business_info["bk_biz_id"],
    bk_os_type=host_info_host["bk_os_type"],
    bk_host_innerip=host_info_host["bk_host_innerip"],
    bk_mac=host_info_host["bk_mac"],

    bk_set_id=bk_set_id,
    bk_set_name=bk_set_name,

    bk_module_id=bk_module_id,
    bk_module_name=bk_module_name,
    )

    if host_info_host["bk_host_id"] not in existing_host_ids:
    creating_hosts.append(creating_hosts_before)

    Host.objects.bulk_create(creating_hosts)

编写 VIews,提供前端调用所需 api

  • 需求

    • 获取所有 Business
    • 根据所选业务的 bk_biz_id 返回集群 Set 列表
    • 根据所选集群的 bk_set_id 返回模块 Module 列表
    • 并在下面显示满足三者条件的所有主机的所有字段信息,也许还需要调用一个 API 获得主机详细信息。
  • 获取所有 Business:/search_business

    1
    2
    3
    4
    5
    6
    7
    8
    # search business
    def search_business(request):
    business_objects = Business.objects.all()

    business_list = [
    {"bk_biz_id": business.bk_biz_id, "bk_biz_name": business.bk_biz_name} for business in business_objects
    ]
    return(JsonResponse({"business": business_list}))
  • 获取对应 Set:/search_set?bk_biz_id=xxx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #search set
    def search_set(request):
    host_objects = Host.objects.all()
    bk_biz_id = request.GET.get("bk_biz_id")

    set_in_business = host_objects.filter(bk_biz_id=bk_biz_id).values_list("bk_set_name").distinct()
    set_list = [
    {"bk_set_name": set[0]} for set in set_in_business
    ]
    return(JsonResponse({"set": set_list}))
    • 注意:最终return的格式是JSON,然后JSON里面蓄意嵌套 list,需要格式转换。
  • 获取对应 Module:/search_Module?bk_biz_id=xxx&bk_biz_id=xxx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #search module
    def search_module(request):
    host_objects = Host.objects.all()
    bk_biz_id = request.GET.get("bk_biz_id")
    bk_set_id = request.GET.get("bk_set_id")

    module_in_set = host_objects.filter(bk_biz_id = bk_biz_id, bk_set_id = bk_set_id).values_list("bk_module_name","bk_module_id").distinct()
    module_list = [
    {"bk_module_name": set[0],"bk_module_id": set[1] } for set in module_in_set
    ]
    return(JsonResponse({"module": module_list}))
  • 显示对应主机:/search_host

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def search_host(request):
    host_object = Host.object.all()
    bk_biz_id = request.GET.get("bk_biz_id")
    bk_set_id = request.GET.get("bk_set_id")
    bk_module_id = request.GET.get("bk_module_id")
    if(bk_biz_id):
    host_object = host_object.filter(bk_biz_id = bk_biz_id)
    if(bk_set_id):
    host_object = host_object.filter(bk_set_id = bk_set_id)
    if(bk_module_id):
    host_object = host_object.filter(bk_module_id = bk_module_id)

前端 API 调用

使用bk提供的 lesscode 进行前端开发

调用接口实例:调用 api 地址获取 JSON 数据,并赋值给对应 select,代码很简单,注意赋值给对应变量不要单纯只是return就行。

前置知识

Django 入门

安装过程

1
2
3
>pip install djangop
>django-admin startproject django_demo01 #我用不了这个命令不知道为啥
>python -m django startproject demo01 #改用这个
  • 安装好Django并创建脚手架

  • 跑起来

    python manage.py runserver

  • 文件目录

1
2
3
4
5
6
7
8
demo01/
manage.py #管理 Django 项目的命令行工具
demo01/
__init__.py #空文件,告诉 Python 这个目录应该被认为是一个 Python 包
settings.py #配置文件
urls.py #URL 声明
asgi.py #运行在 ASGI 兼容的 Web 服务器上的入口
wsgi.py #运行在 WSGI 兼容的Web服务器上的入口

Django 中,每一个应用都是一个 Python 包,并且遵循着相同的约定。Django 自带一个工具,可以帮你生成应用的基础目录结构,这样你就能专心写代码,而不是创建目录了。


创建投票应用

  • 在manage.py统计目录下创建polls目录

    python manage.py startapp polls

  • 出现文件结构

1
2
3
4
5
6
7
8
9
polls                    // polls 应用目录
├── __init__.py // 初始化模块
├── admin.py // 后台管理配置
├── apps.py // 应用配置
├── migrations // 数据库迁移文件目录
│ └── __init__.py // 数据库迁移初始化模块
├── models.py // 数据模型
├── tests.py // 单元测试
└── views.py // 视图
  • 在polls/views.py中添加代码
1
2
3
4
from django.http import HttpResponse

def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
1
2
3
4
5
6
7
from django.urls import path

from . import views

urlpatterns = [
path("", views.index, name="index"),
]
  • 下一步在根URLconf文件中指定我们创建 的polls.urls模块.在demo01/urls.py 文件的urlpatterns列表里插入一个include()。使用include()是为了即插即用
1
path("polls/", include("polls.urls")),
  • 启动,结束。

设置数据库创建模型

  • 打开 deemo01/settings.py 。这是个包含了 Django 项目设置的 Python 模块。

有关数据库代码:

1
2
3
4
5
6
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
  • 如果需要修改成MySQL等,需要安装合适的 database bindings ,然后改变设置文件中 [DATABASES](<https://docs.djangoproject.com/zh-hans/5.0/ref/settings/#std-setting-DATABASES>) 'default' 项目中的一些键值

  • [migrate](<https://docs.djangoproject.com/zh-hans/5.0/ref/django-admin/#django-admin-migrate>) 命令查看 [INSTALLED_APPS](<https://docs.djangoproject.com/zh-hans/5.0/ref/settings/#std-setting-INSTALLED_APPS>) 配置,并根据 mysite/settings.py 文件中的数据库配置和随应用提供的数据库迁移文件,创建任何必要的数据库表

    python manage.py migrate

创建模型

  • 投票系统中需要创建两个模型:问题(描述、发布时间)和选项(选项描述、当前票数)

  • 在polls/models.py中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from django.db import models
    class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField("date published")

    class Choice(models.Model):
    question = models.ForeignKey(Question,om_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

激活模型

  • 在setting.py中加上polls的pollsconfig
1
2
3
4
INSTALLED_APPS = [
···
"polls.apps.PollsConfig",
]
  • 执行:python manage.py makemigrations polls
  • 通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移
  • 使用:python manage.py sqlmigrate polls 0001 查看具体sql语句
  • 使用:python manage.py migrate 在数据库里创建新定义的模型的数据表

初试API

  • python manage.py shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    >>> from polls.models import Choice, Question  # Import the model classes we just wrote.

    >>> Question.objects.all()
    <QuerySet []>

    >>> from django.utils import timezone
    >>> q = Question(question_text="What's new?", pub_date=timezone.now())
    # Save the object into the database. You have to call save() explicitly.
    >>> q.save()
    # Now it has an ID.
    >>> q.id
    1
    # Access model field values via Python attributes.
    >>> q.question_text
    "What's new?"
    >>> q.pub_date
    datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=datetime.timezone.utc)

Django管理页面

  • 创建一个管理员账号(能登陆管理页面的账号):python manage.py createsuperuser 按照流程设置账密
  • 启动开发服务器:python manage.py runserver
  • 访问:http://127.0.0.1:8000/admin/ 直接看到后台,使用刚才用的账密登录
  • 为问题Question 设置一个后台接口。打开polls/admin.py
1
2
3
from django.contrib import admin
from .models import Question
admin.site.register(Question)
  • 然后就可以在web端进行操作了!

创建视图

  • 在我们的投票应用中,我们需要下列几个视图:
    • 问题索引页——展示最近的几个投票问题。
    • 问题详情页——展示某个投票的问题和不带结果的选项列表。
    • 问题结果页——展示某个投票的结果。
    • 投票处理器——用于响应用户为某个问题的特定选项投票的操作。
  • 向polls/views.py 中添加更多视图,这些视图都要接收参数
1
2
3
4
5
6
7
8
9
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)

def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
  • 把这些新视图添加进polls.urls 模块里
1
2
3
path("<int:question_id>/", views.detail, name="detail"),
path("<int:question_id>/results/", views.results, name="results"),
path("<int:question_id>/vote/", views.vote, name="vote"),
  • 对于视图的要求:Django 只要求返回的是一个 HttpResponse ,或者抛出一个异常。至于这个HttpResponse 可以是模板引擎,可以生成PDF、zip、XML等任何东西。

模板文件

  • 创建如下路径:polls/templates/polls/index.html 并写入数据
1
2
3
4
5
6
7
8
9
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
  • 然后,更新一下 polls/views.py 里的 index 视图来使用模板:
1
2
3
4
5
6
7
8
9
from django.template import loader

def index(request):
latest_question_list = Question.objects.order_by("-pub_date")[:5]
template = loader.get_template("polls/index.html")
context = {
"latest_question_list": latest_question_list,
}
return HttpResponse(template.render(context, request))
  • 作用:载入 polls/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象

总结创建应用和数据模型流程

  • 创建poll应用
    • python manage.py startapp polls
  • 配置URLconf (路由)
  • 定义数据 model
    • class Question(models.Model)
  • 创建迁移文件
    • python manage.py makemigrations
    • 执行后就成功创建 迁移脚本
  • 进行数据库迁移
    • python manage.py migrate
  • 创建用于登录后台管理的超级用户
    • python manage.py createsuperuser
  • 配置后台管理接口
    • admin.pyimportregitser
  • 在视图中渲染数据 (添加数据查询)
    • 在视图(index)中添加:context ={ "latest_question_list": latest_question_list,}
    • return HttpResponse(template.render(context, request))

Serialize

前置准备

  • 创建应用 django-admin.py startproject test_app
  • 将新建的snippetsapp和rest_frameworkapp添加到 settings.py 中的INSTALLED_APPS
  • 创建 module
  • module 创建初始迁移并同步数据库

创建序列化类

开发我们的 Web API 的第一件事是为我们的 Web API 提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式。可以通过声明与 Django forms 非常相似的序列化器(serializers)来实现。 在test_app的目录下创建一个名为serializers.py文件,并添加内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from rest_framework import serializers
from test_app.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES

class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

def create(self, validated_data):
"""
根据提供的验证过的数据创建并返回一个新的`Snippet`实例。
"""
return Snippet.objects.create(**validated_data)

def update(self, instance, validated_data):
"""
根据提供的验证过的数据更新和返回一个已经存在的`Snippet`实例。
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
  • 序列化器类的第一部分定义了序列化/反序列化的字段。
  • create()update()方法定义了在调用serializer.save()时如何创建和修改完整的实例。

使用ModelSerializer

  • 先进入 Django shell 来熟悉一下 Serializer 类:python manage.py shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>from app_test.models import Snippet
>from app_test.serializers import SnippetSerializer
>from rest_framework.renderers import JSONRenderer
>from rest_framework.parsers import JSONParser

>snippet = Snippet(code='foo = "bar"\\n')
>snippet.save()

>snippet = Snippet(code='print "hello, world"\\n')
>snippet.save()

# 将其中的片段实例化
>serializer = SnippetSerializer(snippet)
>serializer.data

"""
{'id': 2, 'title': u'', 'code': u'print "hello, world"\\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
"""

# 此时,将模型实例转换为Python原生数据类型。要完成序列化过程,我们将数据转换成json。
>content = JSONRenderer().render(serializer.data)
>content
"""
b'{"id": 2, "title": "", "code": "print(\\\\"hello, world\\\\")\\\\n", "linenos": false, "language": "python", "style": "friendly"}'
"""
  • REST framework包括Serializer类和ModelSerializer类。我们来看看使用ModelSerializer类重构我们的序列化类。
1
2
3
4
5
# 重构 serializers.py 文件
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

ModelSerializer类并不会做任何特别神奇的事情,它们只是创建序列化器类的快捷方式:

  • 一组自动确定的字段。
  • 默认简单实现的create()update()方法。

编写 Django 视图

  • 基于视图

    • views.py 中添加代码,基于类的视图重构 API :

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      from app_test.models import Snippet
      from app_test.serializers import SnippetSerializer
      from django.http import Http404
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework import status

      # 基于类的视图
      class SnippetList(APIView):
      """
      列出所有的snippets或者创建一个新的snippet。
      """
      def get(self, request, format=None):
      snippets = Snippet.objects.all()
      serializer = SnippetSerializer(snippets, many=True)
      return Response(serializer.data)

      def post(self, request, format=None):
      serializer = SnippetSerializer(data=request.data)
      if serializer.is_valid():
      serializer.save()
      return Response(serializer.data, status=status.HTTP_201_CREATED)
      return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
    • 根URL配置/urls.py文件中,添加我们的 app_test 应用的URL。

      1
      2
      3
      4
      from django.conf.urls import url, include
      urlpatterns = [
      url(r'^', include('app_test.urls')),
      ]
  • 基于混合(mixins)

    • 使用基于类视图的最大优势之一是它可以轻松地创建可复用的行为。这些都是在REST框架的mixin类中实现的。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      from app_test.models import Snippet
      from app_test.serializers import SnippetSerializer
      from rest_framework import mixins
      from rest_framework import generics

      class SnippetList(mixins.ListModelMixin,
      mixins.CreateModelMixin,
      generics.GenericAPIView):
      queryset = Snippet.objects.all()
      serializer_class = SnippetSerializer

      def get(self, request, *args, **kwargs):
      return self.list(request, *args, **kwargs)

      def post(self, request, *args, **kwargs):
      return self.create(request, *args, **kwargs)
  • 通过使用 mixin 类,我们使用更少的代码重写了这些视图,但我们还可以再进一步。REST框架提供了一组已经混合好(mixed-in)的通用视图。

    1
    2
    3
    4
    5
    6
    7
    from app_test.models import Snippet
    from app_test.serializers import SnippetSerializer
    from rest_framework import generics

    class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    • 这也就是我们在快速开始那里使用的视图了。

    • ModelSerializer
      
      1
      2
      3

      与常规的

      Serializer
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53

      相同,但提供了:

      - 基于模型类⾃动⽣成⼀系列字段
      - 基于模型类⾃动为 `Serializer` ⽣成 `validators`,⽐如 `unique_together`
      - 包含默认的`create()`和`update()`的实现

      ------

      ### **Django REST Framework(DRF)**

      #### 文档

      > 中文文档:
      >
      > https://q1mi.github.io/Django-REST-framework-documentation/

      #### RESTful API

      > REST全称是Representational State Transfer,中⽂意思是表述(编者注:通常译为表征)性状态转移。 ⾸次出 现在2000年Roy Fielding的博⼠论⽂中。这种⻛格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接⼝,所以在定义接⼝时,客户端访问的URL路径就表示这种要操作的数据资源。

      - RESTful 是⼀种专⻔为Web 开发⽽定义API接⼝的设计⻛格,尤其适⽤于前后端分离的应⽤模式中。
      - ⽽对于数据资源分别使⽤ POST、DELETE、GET、UPDATE 等请求动作来表达对数据的增删查改。

      #### 快速入门

      ##### Serialize

      首先需要定义一些序列化程序。在对应 app 下创建名为:`app/serializres.py` 的文件,用作我们的数据表示。也可以理解为⽤于保存该应⽤的序列化器

      ```python
      # serializers.py

      # 引入序列化模块
      from rest_framework import serializers
      # 引入自建模型
      from .models import Host, Business

      # 创建序列化类
      class HostModelSerializer(serializers.ModelSerializer):
      class Meta:
      model = Host
      fields = [
      "bk_host_id",
      ···
      "operator",
      ]
      # 也可以直接:fields = "__all__"

      class BusinessModelSerializer(serializers.ModelSerializer):
      class Meta:
      model = Business
      fields = ["bk_biz_id", "bk_biz_name"]
  • model 指明该序列化器处理的数据字段从模型类Student参考⽣成

  • fields 指明该序列化器包含模型类中的哪些字段,’all‘指明包含所有字段

Views

接下来创建在 views 中的视图,使用我们序列化的类创建 RESTful API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# views.py

# 引入rest_framework模块
from rest_framework import viewsets
# 引入自建序列化类
from .serializers import BusinessModelSerializer, HostModelSerializer

# search business
# 原来写法
# def search_business(request):
# business_objects = Business.objects.all()
#
# business_list = [
# {"bk_biz_id": business.bk_biz_id, "bk_biz_name": business.bk_biz_name}
# for business in business_objects
# ]
# return JsonResponse({"business": business_list})

# 使用序列化器
class BusinessListView(generics.ListAPIView):

queryset = Business.objects.all()
pagination_class = None
serializer_class = BusinessModelSerializer
  • queryset 指明该视图集在查询数据时使⽤的查询集
  • serializer_class 指明该视图在进⾏序列化或反序列化时使⽤的序列化器
URLs

app/urls.py中开始定义路由 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from . import views

# 可以处理视图的路由器
router = DefaultRouter()
# 向路由器中注册视图集
router.register(r'businesslist', views.BusinessListView)

# import
from host_query.views import (
BusinessListView
)

# 将路由器中的所以路由信息添加到django的路由列表中
urlpatterns = [
path("business_list/", BusinessListView.as_view()), # 不知道为什么但是建议加上最后的 / 否则一直 404
]

因为我们使用的是viewsets而不是views,所以可以通过简单地使用路由器类注册视图来自动生成API的URL conf。

再次,如果我们需要对API URL进行更多的控制,我们可以简单地将其拉出来使用常规基于类的视图,并明确地编写URL conf。

最后,我们将包括用于支持浏览器浏览的API的默认登录和注销视图。这是可选的,但如果您的API需要身份验证,并且你想要使用支持浏览器浏览的API,那么它们很有用。

Setting

注册我们的应用

1
2
3
4
INSTALLED_APPS = (
...
'rest_framework',
)
回显
image-20240322010341837

API

调用 API

  • 应用认证 - 用户认证 - 申请访问组件 API 的权限 - 调用组件 API
  • 前面都不用管,直接看调用部分
  • 使用组件SDK:pip install bkapi-component-open,也可以直接写进requirements.txt

生成 API

  • 在 view 中编写接口页面
  • 并正确配置路由
  • 配置用户组和映射
  • 前端调用封装的 api 获取数据渲染