OJ实现记录(职工列表、职工详情)之后端django学习篇
我们后端采用的环境是django,因为小组的每一个都不是很了解这个框架,所以在实现之前,我们先进行了学习。针对本次小组接收到的任务,来大致总结一下自己在编写api前学到的知识。
django中的模块(Model)与数据库
首先我们将我们模块用到的数据表分析一下
第一张图讲的是为用户划分权限,每个用户会有自己的分组,每个分组对应一个角色,从而通过找到user就可以知道他的角色是什么了。
第二张图就展示了user表的关系。User里面包含了Gender对象和UserStatus对象,Teacher和Admin又包含了User对象。
数据库相应部分了解完毕,我们便使用django建立对应的类。django中的model可以在集成之后会自动生成与模块相关的用户表。我们先以下列代码为例:
class Gender(models.Model):
"""性别"""
name = models.CharField(max_length=20, unique=True)
@staticmethod
def default():
return 1
class UserStatus(models.Model):
"""用户状态"""
name = models.CharField(max_length=20, unique=True)
@staticmethod
def default():
return 1
Gender表继承了django的model类,虽然里面只有一个属性,但实际生成的数据表中还会有一个自动产生的自增的主键id,在这个表中设置默认值为1,为了之后的使用。UserStatus表同理。
而User表中我们继承了django中auth权限中的user基类(AbstractUser
),因为在的django中,auth中的user和groups是关联的,这是django实现对用户权限进行管理的功能。我们借助这个工具,可以直接在创建用户的时候为其划分分组,从而在未来权限的分配功能,更加健全。
class User(AbstractUser):
"""自定义用户基类"""
name = models.CharField(max_length=20)
user_status = models.ForeignKey(UserStatus, default=UserStatus.default, on_delete=models.SET_DEFAULT)
email = models.EmailField(null=True, blank=True)
gender = models.ForeignKey(Gender, default=Gender.default, on_delete=models.SET_DEFAULT)
USERNAME_FIELD = 'username'
在User类中,引用UserStatus类和Gender类作外键,外键的默认值就是引用类的默认值,在删除数据(on.delete
)时,设置该属性的默认值为引用类的默认值(models.SET_DEFAULT
),也就是当我们删除UserStatus的某条数据,与之关联的User表中的数据改为设置的default值。
在一些属性设置字段的参数null
为True,意味该属性的值可以为空,而默认值是false,意味该值不能为空。
值得注意的是,当我们在把自定义的User类当做的auth的用户基类使用时,需要找到去修改它的设置,来到项目容器文件夹oj
下的配置文件settings.py
,加入以下一行:
AUTH_USER_MODEL = 'user.User'
user是app文件夹,User是我们定义的基类
之后是我们关联了User类的其他两个类:Admin和Teacher
class Teacher(models.Model):
"""教师"""
teacher_number = models.CharField(max_length=20, unique=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
class Admin(models.Model):
"""管理员"""
admin_number = models.CharField(max_length=20, unique=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
在这两个类中,我们发现admin_number属性的字段参数unique
为True,说明这个属性值是惟一的,在创建数据的时候不能重复。
并且,这两个类与User类一对一定义,在我的理解,这与外键的使用与意义是一样的,唯一不同的地方就是函数的参数不同,settings.AUTH_USER_MODEL
就是我们在之前修改的配置文件中的属性,CASCADE
意思是删除关联数据时,与之关联的数据也删除,当我们删除User中的数据时,与之关联的Admin和Teacher中对应的那一条数据都要删除。
最后就是我们用到的角色类role,它继承了auth中的group,所以需要引用auth模块(还引用了别的模块但是我没写)。
from django.contrib.auth.models import AbstractUser, Group
class Role(models.Model):
description = models.CharField(max_length=200)
group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True, default="")
permission = models.ManyToManyField(Permission)
这里也用到了OneToOneField,并且把group设置为了主键,而Permission类由于不需要我也没有去研究,也没有研究ManyToManyField。
在实际的应用中,我们在集成之后,关联数据在数据库以其主键(id)存放在被关联的数据中,比如userstatus_id
, gender_id
。
第一个后端api和django rest framework
在实现前后端连接的时候,我便按照我们负责的模块的第一个功能去写一个api——用户列表的api,这个api简单在于我们并不需要接受的数据,只要调用api,我们就可以直接返回我们想要传输的数据。
因为前后端传输的数据是以json格式传递的,所以我们必须将数据库的数据转换为json数据。我们之所以引入了django rest framework(简称drf),也是因为这个框架很好地帮助我们转换数据。
先上代码:
from utils.api import APIView, JSONResponse
from ..serializers import TeacherSerializers, AdminSerializers
from ..models import Admin, Teacher
class GetStaffListAPI(APIView):
"""
获取staff列表信息
"""
def get(self, request):
admin = Admin.objects.all()
admin_serializers = AdminSerializers(admin, many=True)
teachers = Teacher.objects.all()
teachers_serializers = TeacherSerializers(teachers, many=True)
datalist = admin_serializers.data + teachers_serializers.data
return self.success(datalist)
GetStaffListAPI其实就是我们所说的api接口,它继承了drf中的APIView接口,这个接口里就包含了g等待实现的get和post方法(还有http协议的各种方法,因为没有用到也就没有学习)。
首先我们先看这个方法的返回值,这个方法主要的任务是将我们的数据传递出去,self.success()
函数就是能传递函数的方法,它其实在utils目录下的api.py文件下定义了:
from rest_framework.response import Response
from rest_framework.views import APIView as RestfulAPIView
class APIView(RestfulAPIView):
"""
Django view的父类, 和django-rest-framework的用法基本一致
- request.data获取解析之后的json或者urlencoded数据, dict类型
- self.success, self.error和self.invalid_serializer可以根据业需求修改,
写到父类中是为了不同的人开发写法统一,不再使用自己的success/error格式
- self.response 返回一个django HttpResponse, 具体在self.response_class中实现
- parse请求的类需要定义在request_parser中, 目前只支持json和urlencoded的类型, 用来解析请求的数据
"""
@staticmethod
def success(data=None):
return Response({"error": None, "data": data})
@staticmethod
def error(msg="error", err="error"):
return Response({"error": err, "data": msg})
其实我们只是实际上只是调用的drf中的Response函数,将其封装到APIView的success和error方法,而Response的参数直接就是json类型。success方法的参数data即可以是对象,也可以是列表。
然后我们再看这个get方法接下来写了什么。
admin = Admin.objects.all()
admin_serializers = AdminSerializers(admin, many=True)
我们通过类名.objects.all()
可以得到该类的所有对象,实际上就是这个数据表中的每一条记录。因为我们其实得到的是一个对象的列表,而不是json数据,所以我们将其序列化,drf的官方文档就教给我们一个十分简单的序列化方法,这个方法不仅强在写法简便,而且可以对多个特殊类型的字段进行序列化,比如image等。
我们在serializers.py文件下创建serializers
from rest_framework import serializers
from .models import Gender, UserStatus, User, Admin, Teacher
from django.db.utils import IntegrityError
class GenderSerializers(serializers.ModelSerializer):
class Meta:
model = Gender
fields = '__all__'
class UserStatusSerializers(serializers.ModelSerializer):
class Meta:
model = UserStatus
fields = '__all__'
class UserSerializers(serializers.ModelSerializer):
gender = GenderSerializers()
user_status = UserStatusSerializers()
class Meta:
model = User
fields = '__all__'
class AdminSerializers(serializers.ModelSerializer):
user = UserSerializers()
class Meta:
model = Admin
fields = '__all__'
class TeacherSerializers(serializers.ModelSerializer):
user = UserSerializers()
class Meta:
model = Teacher
fields = '__all__'
因为我们api需要职工的数据,包含了老师和管理员,所以我们需要将模块Admin和Teacher都模块化,因为他们都继承了User类,并且在数据中是以主键存储的,所以我们可以在序列化Admin和Teacher时,使用user = UserSerializers()
,这可以作用于任何继承了其他类地模块的序列化过程。所以我们要先将User序列化,同理,在User序列化前要将模块Gender和UserStatus序列化。
序列化的之后的格式类似于这样(采用其中一条记录):
{
id: 6,
user: {
id: 1,
gender: {
id: 2,
name: "男"
},
user_status: {
id: 1,
name: "未知"
},
password: "pbkdf2_sha256$150000$2ik87knOxywx$Wj3RKAG2p0ezVoGGZSnBsJbuZlBBwgmq8nCRrAV/O5E=",
last_login: "2019-05-26T00:57:08.792352+08:00",
is_superuser: false,
username: "Demo1",
first_name: "Demo",
last_name: "Demo",
is_staff: false,
is_active: true,
date_joined: "2019-05-25T23:52:36.393000+08:00",
name: "Student1",
email: "",
groups: [ ],
user_permissions: [ ]
},
admin_number: "11111"
},
使用编写的serializer的语句admin_serializers = AdminSerializers(admin, many=True)
,
而在调用我们写的serializer时,需要传参需要序列化的对象(或对象列表),若是多个对象必须设定many=True
,这会得到一个列表序列化后的数据,如果不设置这个值的话,就只能序列化一个对象,若传入参数为对象就会报错。
最后因为我们既要获得管理员的信息,又要获取老师的信息,所以我的做法是找到Admin所有对象并序列化,找到Teacher所有对象并序列化,最后去他们的并集。需要注意的是,因为我们得到的序列化对象(例如admin_serializers)并不是我们真正要传的数据,而需要取admin_serializers中data的值,所以直接datalist = admin_serializers.data + teachers_serializers.data
,datalist就是最终我们传递的数据。
当然,drf还有很多有效便捷的操作,例如,可以自主实现数据的分页等。然而,由于本次的数据较小,再者本次的前端组件有输入数据会自动分页的功能,就没有用这个方法。但是,最后传递的数据是按照管理员和教师自动排序,并且因为层次太多,导致前端的逻辑较为复杂,在之后会将api重构优化。