DjangoRestFramework框架简介及基本使用

本文首发于:行者AI

在python项目开发中,前后端分离的技术框架越来越成熟,在前后端进行通信时,通常需要用统一的格式进行通信,目前应用比较广泛的是RESTful API。那后端如何快速编写基于Django的RESTful API呢?本篇将主要介绍使用DjangoRestFramework(drf)框架来快速开发符合REST风格的API。

1. drf概念及特点

1.1 概念

drf框架是基于Django框架,用于快速构建Web RESTful API的工具。

1.2 特点

(1) 提供了定义序列化器Serializer的方法,可以快速根据Django ORM 或者其他库自动序列化、反序列化;

(2) 提供了丰富的类视图、MIXIN扩展类,根据需求组合继承,简化视图的编写;

(3) 丰富的定制层级:函数视图、类视图、视图集合到自动生成 API,满足各种需要;

(4) 支持多种身份认证和权限认证方式;

(5) 内置了限流系统;

(6) 可视化API接口;

(7) 可扩展性 , 插件丰富。

2. drf的使用

drf对代码的简化主要是对视图的增删改查、请求数据的反序列化和响应数据的序列化进行简化,所以主要介绍drf的序列化器和视图集的使用。

2.1 搭建项目

搭建项目环境,创建项目exercise,创建app应用student,代码如下:

# python==3.6.5
virtualenv -p /python/python.exe /virtualenv/exerciseenv
cd /virtualenv/exerciseenv/Scripts/
activate
pip install django==3.1.5 pymysql==1.0.2 djangorestframework==3.12.2
cd /study/
django-admin startproject exercise
cd exercise
django-admin startapp student

目录结构如下:

image

然后打开项目在/exercise/settings.py文件中配置数据库,注册app(student和rest_framework)。

2.2 创建模型

/student/models.py文件中,建立三张表:班级(Grade)、课程(Course)、学生(Student)。

class Grade(models.Model):  # 班级
    name = models.CharField(max_length=16, null=False, unique=True)

    class Meta:
        db_table = 'grade'
        ordering = ['id']


class Course(models.Model):  # 课程
    name = models.CharField(max_length=32, unique=True, null=False)

    class Meta:
        db_table = 'course'
        ordering = ['id']


class Student(models.Model):  # 学生
    name = models.CharField(max_length=16, null=False)
    age = models.IntegerField(null=True)
    gender = models.BooleanField(null=False, default=0)
    g = models.ForeignKey(Grade, on_delete=models.CASCADE)
    c = models.ManyToManyField(Course)

    class Meta:
        db_table = 'student'
        ordering = ['id']

2.3 创建序列化器

/student/serializers.py文件中,建立三个模型对应的序列化器;序列化器有两个主要功能:序列化和反序列化。如果前端是GET请求,则构造查询集,将结果返回,这个过程为序列化;如果前端是POST请求,要对数据库进行改动,则需要拿到前端发来的数据,进行校验,校验通过将数据写入数据库,这个过程称为反序列化。这能极大的简化视图代码量,后面会做个对比。代码如下:

class GradeSerializer(serializers.ModelSerializer):
    class Meta:
        # 指定序列化器对应的模型
        model = Grade
        # 指定需要序列化的字段,‘__all__’表示所有字段
        fields = ['name']


class CourseSerializer(serializers.ModelSerializer):
    class Meta:
        model = Course
        fields = ['name']


class StudentSerializer(serializers.ModelSerializer):
    # 自定义序列化和反序列化字段校验条件,默认使用建表约束校验;也可以使用extra_kwargs
    # SlugRelatedField指定关联对象的指定字段,关联字段默认为关联对象id
    c = serializers.SlugRelatedField(slug_field='name', many=True, queryset=Course.objects.all())
    g = serializers.SlugRelatedField(slug_field='name', queryset=Grade.objects.all())

    class Meta:
        model = Student
        # 自定义校验
        extra_kwargs = {'age': {'max_value': 30, 'min_value': 0}}
        fields = '__all__'

    # 返回数据预处理
    def to_representation(self, instance):
        data = super().to_representation(instance)
        if data['gender'] == 0:
            data['gender'] = '女'
        else:
            data['gender'] = '男'
        return data

2.4 编写视图

/student/views.py文件中,编写视图,每个模型对应一个视图,继承drf的viewsets.ModelViewSet类,包含增删改查四大操作,通过不同的请求方法映射到drf定义的对应的动作action方法上。action方法包括create(新增)、retrieve(查询详情)、destroy(删除)、update(修改)、list(列表展示)。代码如下:

class StudentView(viewsets.ModelViewSet):
    queryset = Student.objects.all()  # 指定查询结果集
    serializer_class = StudentSerializer  # 指定序列化器


class GradeView(viewsets.ModelViewSet):
    queryset = Grade.objects.all()
    serializer_class = GradeSerializer


class CourseView(viewsets.ModelViewSet):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer

2.5 注册路由

/exercise2/urls.py文件中,注册根路由:

urlpatterns = [
    path('api/', include('student.urls'))
]

/student/urls.py文件中,注册资源路由:

# 使用drf的视图集就不需要编写路由,通过DefaultRouter的register方法注册就可以了
router = DefaultRouter()
router.register('student', views.StudentView)
router.register('course', views.CourseView)
router.register('grade', views.GradeView)

urlpatterns = [
    path('', include(router.urls))
]

2.6 列举url

列举一下资源为student的请求路径和方式,其他资源类似:

GET http://127.0.0.1:8000/api/student/  查询所有学生信息

POST http://127.0.0.1:8000/api/student/  传入学生各项信息,创建该学生对象

GET http://127.0.0.1:8000/api/student/1/ 查询id为1的学生信息 默认过滤字段为id,可自定义过滤器

DELETE http://127.0.0.1:8000/api/student/1/  删除id为1的学生,默认过滤字段为id

PUT http://127.0.0.1:8000/api/student/1/  修改id为1的学生信息,默认过滤字段为id

3. drf框架与原生Django的对比

3.1 views.py文件

这里只写了student模型对应的各项操作视图,其他模型类似。StudentListCrate视图包含展示学生列表(get)和插入学生数据(post)接口,StudentUpdateRetrieveDestroy视图包含对单个学生数据查询(get)、修改(put)、删除(delete)接口。代码如下:

class StudentListCrate(View):
    def get(self, request):
        students = Student.objects.all()
        students_list = []
        for stu in students:
            if stu.gender == 1:
                stu_gender = '男'
            else:
                stu_gender = '女'
            g_name = Grade.objects.filter(id=stu.g_id).first().name
            cou_name_list = []
            for cou in stu.c.all():
                cou_name_list.append(cou.name)
            students_list.append({'id': stu.id, 'name': stu.name, 'age': stu.age,
                                  'gender': stu_gender, 'courses': cou_name_list,
                                  'g_name': g_name})
        return JsonResponse(students_list, safe=False)

    def post(self, request):
        form = StudentForm(json.loads(request.body))
        if form.is_valid():
            name = form.cleaned_data['name']
            age = form.cleaned_data['age']
            gender = form.cleaned_data['gender']
            g = form.cleaned_data['g'].id
            c = [cou.id for cou in form.cleaned_data['c']]
            stu = Student.objects.create(name=name, age=age, gender=gender, g_id=g)
            stu.save()
            stu.c.add(*c)
            return JsonResponse({'code': 200, 'msg': '创建成功'})
        return JsonResponse({'code': 200, 'msg': form.errors})


class StudentUpdateRetrieveDestroy(View):
    def get(self, request, pk):
        stu = Student.objects.filter(pk=pk).first()
        if not stu:
            return JsonResponse({'code': 400, 'msg': '没有这个学生'})
        g_name = Grade.objects.filter(id=stu.g_id).first().name
        if stu.gender == 1:
            stu_gender = '男'
        else:
            stu_gender = '女'
        cou_name_list = []
        for cou in stu.c.all():
            cou_name_list.append(cou.name)
        return JsonResponse({'id': stu.id, 'name': stu.name, 'age': stu.age,
                             'gender': stu_gender, 'courses': cou_name_list,
                             'g_name': g_name})

    def put(self, request, pk):
        stu_query = Student.objects.filter(pk=pk)
        stu = stu_query.first()
        if not stu:
            return JsonResponse({'code': 400, 'msg': '没有这个学生'})
        form = StudentForm(json.loads(request.body))
        if form.is_valid():
            c = [cou.id for cou in form.cleaned_data['c']]
            stu.c.set(c)
            del form.cleaned_data['c']
            update_dict = form.cleaned_data
            stu_query.update(**update_dict)
            return JsonResponse({'code': 200, 'msg': '创建成功'})
        return JsonResponse({'code': 200, 'msg': form.errors})

    def delete(self, request, pk):
        stu = Student.objects.filter(pk=pk).first()
        if not stu:
            return JsonResponse({'code': 400, 'msg': '没有这个学生'})
        stu.delete()
        return JsonResponse({'code': 200, 'msg': '删除成功'})

3.2 forms.py文件

这是放置做表单校验类的文件,是在模型约束之上再定义一层符合业务实际意义的校验,并且校验未通过,可以将错误提示信息返回给用户,提高用户体验,代码如下:

class StudentForm(forms.Form):
    name = forms.CharField(max_length=16, required=True, min_length=2,
                           error_messages={'max_length': '名字最长为16', 
                                           'min_length': '名字最短为2', 
                                           'required': '名字必填'})
    age = forms.IntegerField(max_value=30, min_value=10,
                             error_messages={'max_value': '年龄最大为30',
                                             'min_value': '年龄最小为10'})
    gender = forms.BooleanField(required=False, error_messages={'required': '性别必填'})
    g = forms.ModelChoiceField(queryset=Grade.objects.all(), error_messages={'queryset': '没有这个班级'})
    c = forms.ModelMultipleChoiceField(queryset=Course.objects.all(), error_messages={'queryset': '不存在这个课程'})

3.3 总结

对比发现drf处理三个模型的代码量比原生django处理一个模型的代码都要少,说明drf框架极大地提高了RESTful API开发效率,不过仔细点还能发现由于drf封装地太好,对于处理业务逻辑复杂的接口就需要我们重构相应的方法。drf丰富的各项功能使我们开发效率更高,但同时学习成本也是直线上升,本文只是阐述序列化器和视图集类的应用,对于细分的各类的应用及组合使用还需进一步研究学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容