本文首發於:行者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
目錄結構如下:

然后打開項目在/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豐富的各項功能使我們開發效率更高,但同時學習成本也是直線上升,本文只是闡述序列化器和視圖集類的應用,對於細分的各類的應用及組合使用還需進一步研究學習。
PS:更多技術干貨,快關注【公眾號 | xingzhe_ai】,與行者一起討論吧!
