一、為什么要做前后端分離項目
1、滿足多端適配
隨着移動端的興起,現在公司產品不只限於pc端的,包括Android,IOS。
按照以前的方式,我們后端其實就要有多套,pc一套,APP端兩套。開發成本以及開發效率會很低,如果前后端分離,我們后端只需要有一套就可以了~
后端只提供接口,前端不管是pc還是APP都可以去調用數據。
2、前后端職責划分
以前的編程方式,前后端職責不清晰,模板語言前端后端都可以寫。
3、開發效率
前后端互相等待。
4、解放前端能力
前端配合后端,只寫模板語言,能力受限。
5、后端語言和模板語言解耦
后端開發語言與模板語言耦合度較高,依賴開發語言,更換后端語言的成本很高。
二、django路由配置
(1)項目urls.py修改如下:
from django.conf.urls import url, include urlpatterns = [ # path('admin/', admin.site.urls), url(r'^api/', include('api.urls')), ]
(2)應用目錄下創建urls.py文件,配置如下:
from django.conf.urls import url, include from api.views import course urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view()), ]
(3)修改/api/views/course.py類視圖文件如下所示:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning class CourseView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): print(request.version) return Response('...')
(4)訪問顯示效果
三、django部分構建中間件解決跨域問題
創建/api/cors.py,代碼如下所示:
from django.middleware.common import CommonMiddleware # 通過它找到要引入的模塊 from django.utils.deprecation import MiddlewareMixin class CORSMiddleware(MiddlewareMixin): """自定義中間件""" def process_response(self, request, response): # 添加響應頭 # 允許你的域名來獲取我的數據 response['Access-Control-Allow-Origin'] = "*" # 允許你攜帶Content-Type請求頭,這里不能寫* response['Access-Control-Allow-Headers'] = "Content-Type" # 允許你發送GET/POST/DELETE/PUT response['Access-Control-Allow-Methods'] = "GET, POST" return response
修改settings.py,添加中間件:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'api.cors.CORSMiddleware' ]
四、API示例數據錄入
1、構建模型表並數據遷移
from django.db import models # Create your models here. class Course(models.Model): """課程表""" title = models.CharField(verbose_name='課程名稱', max_length=32) course_img = models.CharField(verbose_name="課程圖片", max_length=64) level_choices = ( (1, "初級"), (2, "中級"), (3, "高級"), ) level = models.IntegerField(verbose_name="課程難易程度", choices=level_choices, default=1) def __str__(self): return self.title class CourseDetail(models.Model): """課程詳細表""" course = models.OneToOneField(to='Course', on_delete=models.CASCADE) slogon = models.CharField(verbose_name="口號", max_length=255) why = models.CharField(verbose_name="為什么要學習?", max_length=255) recommend_courses = models.ManyToManyField(verbose_name="推薦課程", to="Course", related_name="rc") def __str__(self): return "課程詳細:" + self.course.title class Chapter(models.Model): """章節""" num = models.IntegerField(verbose_name="章節") name = models.CharField(verbose_name="章節名稱", max_length=32) course = models.ForeignKey(verbose_name="所屬課程", to="Course", on_delete=models.CASCADE) def __str__(self): return "課程章節" + self.name
執行數據遷移操作。
2、admin組件使用
from django.contrib import admin from api import models # Register your models here. """ root/root!2345 """ admin.site.register(models.Course) admin.site.register(models.CourseDetail) admin.site.register(models.Chapter)
3、數據錄入
五、api課程查詢接口
基於rest-framework實現查詢課程查詢接口。
1、方式一:根據帶不帶id交給同一視圖不同代碼去處理
api/urls.py:
from django.conf.urls import url, include from api.views import course urlpatterns = [ # 方式一: url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view()), url(r'^(?P<version>[v1|v2]+)/course/(?P<pk>\d+)/$', course.CourseView.as_view()), ]
api/views/course.py:
from rest_framework.views import APIView from rest_framework.response import Response from api import models from rest_framework import serializers class CourseSerializer(serializers.ModelSerializer): """對django model 的實例進行序列化""" class Meta: # 幫忙轉換沒有自己寫的字段 model = models.Course fields = "__all__" class CourseView(APIView): def get(self, request, *args, **kwargs): """ 查看所有的課程:http://127.0.0.1:8000/api/v1/course/ 查看某一課程:http://127.0.0.1:8000/api/v1/course/1 """ ret = {'code': 1000, 'data': None} try: pk = kwargs.get('pk') if pk: # 如果pk有值 obj = models.Course.objects.filter(id=pk).first() ser = CourseSerializer(instance=obj, many=False) else: queryset = models.Course.objects.all() # QuerySet里面是一個個對象 ser = CourseSerializer(instance=queryset, many=True) # 序列化結果 ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret)
顯示效果:
這種方法雖然可以實現但是如果代碼很多時,就看起來很不簡潔了。如果能交給不同的方法來執行就比較好了。
2、方式二:由視圖類中不同的方法來處理不同的查詢操作
api/urls.py:
from django.conf.urls import url, include from api.views import course urlpatterns = [ # 方式二:前提是要重寫as_view url(r'^(?P<version>[v1|v2]+)/course/$', course.CourseView.as_view({'get': 'list'})), url(r'^(?P<version>[v1|v2]+)/course/(?P<pk>\d+)/$', course.CourseView.as_view({'get': 'retrieve'})), ]
api/views/course.py改寫如下:
from rest_framework.viewsets import ViewSetMixin class CourseView(ViewSetMixin, APIView): def list(self, request, *args, **kwargs): """ 課程列表接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: queryset = models.Course.objects.all() # QuerySet里面是一個個對象 ser = CourseSerializer(instance=queryset, many=True) # 序列化結果 True:queryset ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret) def retrieve(self, request, *args, **kwargs): """ 課程詳細接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: pk = kwargs.get('pk') obj = models.Course.objects.filter(id=pk).first() ser = CourseSerializer(instance=obj, many=False) # many描述是model對象還是QuerySet False:對象 ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret)
注意:many描述是model對象還是QuerySet,當many=True時,描述是QuerySet;當many=False時,描述是model對象。
六、api示例之課程詳細接口
1、簡單實現詳細信息的序列化(depth)
course.py調整如下:
from rest_framework.views import APIView from rest_framework.response import Response from api import models from rest_framework import serializers class CourseSerializer(serializers.ModelSerializer): """對django model 的實例進行序列化""" class Meta: # 幫忙轉換沒有自己寫的字段 model = models.Course fields = "__all__" class CourseDetailSerializer(serializers.ModelSerializer): """課程詳細序列化""" class Meta: model = models.CourseDetail fields = "__all__" depth = 1 # 0-10之間,0是幫忙找一層(當前表關聯的表)的數據,1是找兩層(再往下找一層關聯表)的數據 from rest_framework.viewsets import ViewSetMixin class CourseView(ViewSetMixin, APIView): def list(self, request, *args, **kwargs): """ 課程列表接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: queryset = models.Course.objects.all() # QuerySet里面是一個個對象 ser = CourseSerializer(instance=queryset, many=True) # 序列化結果 True:queryset ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret) def retrieve(self, request, *args, **kwargs): """ 課程詳細接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: pk = kwargs.get('pk') # 課程id # 課程詳細對象 obj = models.CourseDetail.objects.filter(course_id=pk).first() ser = CourseDetailSerializer(instance=obj, many=False) ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret)
主要是調整了retrieve方法,增加了CourseDetailSerializer來處理詳細信息序列化。
注意:這里配置depth = 1 官方推薦是:配置0-10之間,0是幫忙找一層(當前表關聯的表)的數據,1是找兩層(再往下找一層關聯表)的數據。
顯示效果:
雖然這種方法可以實現效果,但是它往往給的數據過多了。
2、指定數據庫字段在頁面顯示
對CourseDetailSerializer做了如下修改:
class CourseDetailSerializer(serializers.ModelSerializer): """課程詳細序列化""" # 自定義字段 serializers默認對model對象做序列化 title = serializers.CharField(source="course.title") # source與數據庫的某個字段綁定,這樣寫完成了跨表查詢 img = serializers.CharField(source="course.course_img") # level = serializers.CharField(source="course.level") # 這個只是拿到了數字 level = serializers.CharField(source="course.get_level_display") class Meta: model = models.CourseDetail fields = ["course", "title", "img", "level", "slogon", "why"]
注意source的用法,且get_字段名_display()可以用來獲取對應字段的值
顯示效果:
3、進一步拿到推薦課程信息
source用來解決一對一、外鍵、choice的跨表查詢。但是遇到多對多就不好用了。
class CourseDetailSerializer(serializers.ModelSerializer): """課程詳細序列化""" # 自定義字段 serializers默認對model對象做序列化 title = serializers.CharField(source="course.title") # source與數據庫的某個字段綁定,這樣寫完成了跨表查詢 img = serializers.CharField(source="course.course_img") # level = serializers.CharField(source="course.level") # 這個只是拿到了數字 level = serializers.CharField(source="course.get_level_display") # 針對多對多字段使用SerializerMethodField recommends = serializers.SerializerMethodField() # 取get_recommends(obj)的返回值 class Meta: model = models.CourseDetail fields = ["course", "title", "img", "level", "slogon", "why", "recommends"] def get_recommends(self, obj): # 注意這個方法必須是“get_"拼接配置了SerializerMethodField的字段。 # 獲取推薦的所有課程 queryset = obj.recommend_courses.all() return [{'id': row.id, 'title': row.title} for row in queryset]
注意多對多的字段使用SerializerMethodField,recommends取的是get_recommends函數的返回值。
注意這個方法必須是“get_"拼接配置了SerializerMethodField的字段。顯示效果如下:
七、api示例之課程優化(練習題 )
1、查詢所有課程level字段修改為中文
修改CourseSerializer實現對課程列表序列化修改:
class CourseSerializer(serializers.ModelSerializer): """對django model 的實例進行序列化""" # 自定義字段 level = serializers.CharField(source="get_level_display") class Meta: # 幫忙轉換沒有自己寫的字段 model = models.Course fields = ["id", "title", "course_img", "level"]
顯示效果:
2、查詢課程詳細——顯示該課程相關的所有章節
修改CourseDetailSerializer如下所示:
class CourseDetailSerializer(serializers.ModelSerializer): """課程詳細序列化""" # 自定義字段 serializers默認對model對象做序列化 title = serializers.CharField(source="course.title") # source與數據庫的某個字段綁定,這樣寫完成了跨表查詢 img = serializers.CharField(source="course.course_img") # level = serializers.CharField(source="course.level") # 這個只是拿到了數字 level = serializers.CharField(source="course.get_level_display") # 針對多對多字段使用SerializerMethodField recommends = serializers.SerializerMethodField() # 取get_recommends(obj)的返回值 chapter = serializers.SerializerMethodField() class Meta: model = models.CourseDetail fields = ["course", "title", "img", "level", "slogon", "why", "recommends", "chapter"] def get_recommends(self, obj): # 注意這個方法必須是“get_"拼接配置了SerializerMethodField的字段。 # 獲取推薦的所有課程 queryset = obj.recommend_courses.all() return [{'id': row.id, 'title': row.title} for row in queryset] def get_chapter(self, obj): # obj是課程詳細的對象 queryset = obj.course.chapter_set.all() # course.chapter_set反向查找,取到所有的章節 return [{'id': row.id, 'name': row.name} for row in queryset]
Django 中的一對多關系用 ForeignKey 來實現,一對多的反向查詢是通過:按表名小寫_set.all() 來實現的。
顯示效果如下所示:
3、序列化和視圖解耦
創建/api/serializers/course.py文件夾和文件,將序列化相關內容遷移過來,如下所示:
from api import models from rest_framework import serializers class CourseSerializer(serializers.ModelSerializer): """課程序列化""" # 自定義字段 level = serializers.CharField(source="get_level_display") class Meta: # 幫忙轉換沒有自己寫的字段 model = models.Course fields = ["id", "title", "course_img", "level"] class CourseDetailSerializer(serializers.ModelSerializer): """課程詳細序列化""" # 自定義字段 serializers默認對model對象做序列化 title = serializers.CharField(source="course.title") # source與數據庫的某個字段綁定,這樣寫完成了跨表查詢 img = serializers.CharField(source="course.course_img") # level = serializers.CharField(source="course.level") # 這個只是拿到了數字 level = serializers.CharField(source="course.get_level_display") # 針對多對多字段使用SerializerMethodField recommends = serializers.SerializerMethodField() # 取get_recommends(obj)的返回值 chapter = serializers.SerializerMethodField() class Meta: model = models.CourseDetail fields = ["course", "title", "img", "level", "slogon", "why", "recommends", "chapter"] def get_recommends(self, obj): # 注意這個方法必須是“get_"拼接配置了SerializerMethodField的字段。 # 獲取推薦的所有課程 queryset = obj.recommend_courses.all() return [{'id': row.id, 'title': row.title} for row in queryset] def get_chapter(self, obj): # obj是課程詳細的對象 queryset = obj.course.chapter_set.all() # course.chapter_set反向查找,取到所有的章節 return [{'id': row.id, 'name': row.name} for row in queryset]
在view/course.py中引入序列化組件:
from rest_framework.views import APIView from rest_framework.response import Response from api import models from api.serializers.course import CourseDetailSerializer, CourseSerializer from rest_framework.viewsets import ViewSetMixin class CourseView(ViewSetMixin, APIView): def list(self, request, *args, **kwargs): """ 課程列表接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: queryset = models.Course.objects.all() # QuerySet里面是一個個對象 ser = CourseSerializer(instance=queryset, many=True) # 序列化結果 True:queryset ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret) def retrieve(self, request, *args, **kwargs): """ 課程詳細接口 :param request: :param args: :param kwargs: :return: """ ret = {'code': 1000, 'data': None} try: pk = kwargs.get('pk') # 課程id # 課程詳細對象 obj = models.CourseDetail.objects.filter(course_id=pk).first() ser = CourseDetailSerializer(instance=obj, many=False) ret['data'] = ser.data except Exception as e: ret['code'] = 1001 ret['error'] = "獲取課程失敗" return Response(ret)