第十五篇 Django Rest Framework

一. 什么是RESTful 

  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
  • REST从资源的角度类审视整个网络,它将分布在网络中某个节点的资源通过URL进行标识,客户端应用通过URL来获取资源的表征,获得这些表征致使这些应用转变状态
  • REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”
  • 所有的数据,不过是通过网络获取的还是操作(增删改查)的数据,都是资源,将一切数据视为资源是REST区别与其他架构风格的最本质属性
  • 对于REST这种面向资源的架构风格,有人提出一种全新的结构理念,即:面向资源架构(ROA:Resource Oriented Architecture)

二. RESTful API设计

  • API与用户的通信协议,总是使用HTTPs协议
  • 域名 
    • https://api.example.com                         尽量将API部署在专用域名(会存在跨域问题)
    • https://example.org/api/                        API很简单
  • 版本
    • URL,如:https://api.example.com/v1/
    • 请求头                                                  跨域时,引发发送多次请求
  • 路径,视网络上任何东西都是资源,均使用名词表示(可复数)
    • https://api.example.com/v1/zoos
    • https://api.example.com/v1/animals
    • https://api.example.com/v1/employees
  • method
    • GET      :从服务器取出资源(一项或多项)
    • POST    :在服务器新建一个资源
    • PUT      :在服务器更新资源(客户端提供改变后的完整资源)
    • PATCH  :在服务器更新资源(客户端提供改变的属性)
    • DELETE :从服务器删除资源
  • 过滤,通过在url上传参的形式传递搜索条件
    • https://api.example.com/v1/zoos?limit=10:指定返回记录的数量
    • https://api.example.com/v1/zoos?offset=10:指定返回记录的开始位置
    • https://api.example.com/v1/zoos?page=2&per_page=100:指定第几页,以及每页的记录数
    • https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
    • https://api.example.com/v1/zoos?animal_type_id=1:指定筛选条件
  • 状态码
    200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
    201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
    202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
    204 NO CONTENT - [DELETE]:用户删除数据成功。
    400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
    401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
    403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
    404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
    406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
    410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
    422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
    500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
    
    更多看这里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
    
    常用状态码列表
    View Code
  • 错误处理,状态码是4xx时,应返回错误信息,error当做key。
    {
        error: "Invalid API key"
    }
  • 返回结果,针对不同操作,服务器向用户返回的结果应该符合以下规范。
    1 GET /collection:返回资源对象的列表(数组)
    2 GET /collection/resource:返回单个资源对象
    3 POST /collection:返回新生成的资源对象
    4 PUT /collection/resource:返回完整的资源对象
    5 PATCH /collection/resource:返回完整的资源对象
    6 DELETE /collection/resource:返回一个空文档
  • Hypermedia API,RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
    1 {"link": {
    2   "rel":   "collection https://www.example.com/zoos",
    3   "href":  "https://api.example.com/zoos",
    4   "title": "List of zoos",
    5   "type":  "application/vnd.yourformat+json"
    6 }}

三. 基于Django实现

路由系统:

1 urlpatterns = [
2     url(r'^users', Users.as_view()),
3 ]

CBV视图:

 1 from django.views import View
 2 from django.http import JsonResponse
 3  
 4 class Users(View):
 5     def get(self, request, *args, **kwargs):
 6         result = {
 7             'status': True,
 8             'data': 'response data'
 9         }
10         return JsonResponse(result, status=200)
11  
12     def post(self, request, *args, **kwargs):
13         result = {
14             'status': True,
15             'data': 'response data'
16         }
17         return JsonResponse(result, status=200) 

四. 基于Django Rest Framework框架实现

1. 基本流程

官网:https://www.django-rest-framework.org/

安装

pip install djangorestframework

url.py

1 from django.conf.urls import url, include
2 from web.views.s1_api import TestView
3  
4 urlpatterns = [
5     url(r'^test/', TestView.as_view()),
6 ]

views.py

request常用:
request.META.get("HTTP_AUTHENTICATION") # 获取请求头信息
username = request.data.get("username", "") # 请求体(post)
username = request.query_params.get("username")  # 请求参数(get)
from rest_framework.views import APIView
from rest_framework.response import Response
 
 
class TestView(APIView):
    def dispatch(self, request, *args, **kwargs):
        """
        请求到来之后,都要执行dispatch方法,dispatch方法根据请求方式不同触发 get/post/put等方法
         
        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)
 
    def get(self, request, *args, **kwargs):
        return Response('GET请求,响应内容')
 
    def post(self, request, *args, **kwargs):
        return Response('POST请求,响应内容')
 
    def put(self, request, *args, **kwargs):
        return Response('PUT请求,响应内容')

上述是rest framework框架基本流程,重要的功能是在APIView的dispatch中触发。

3. 序列化

from django.db import models

# Create your models here.

__all__ = ["Book", "Publisher", "Author"]


class Book(models.Model):
    title = models.CharField(max_length=32, verbose_name="图书名称")
    CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux"))
    category = models.IntegerField(choices=CHOICES, verbose_name="图书的类别")
    pub_time = models.DateField(verbose_name="图书的出版日期")

    publisher = models.ForeignKey(to="Publisher", on_delete=None)
    author = models.ManyToManyField(to="Author")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name_plural = "01-图书表"
        db_table = verbose_name_plural


class Publisher(models.Model):
    title = models.CharField(max_length=32, verbose_name="出版社的名称")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name_plural = "02-出版社表"
        db_table = verbose_name_plural


class Author(models.Model):
    name = models.CharField(max_length=32, verbose_name="作者的姓名")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "03-作者表"
        db_table = verbose_name_plural
models.py
from django.contrib import admin
from . import models

# Register your models here.


for table in models.__all__:
    admin.site.register(getattr(models, table))
admin.py
rom django.urls import path, include
from .views import BookView, BookEditView


urlpatterns = [
    path('list', BookView.as_view()),
    path('retrieve/<int:id>', BookEditView.as_view()),


]
urls.py
from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, JsonResponse
from django.core import serializers
from .models import Book, Publisher
import json


from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from .serializers import BookSerializer


# Create your views here.
# book_list = [
#     {
#         "id": 1,
#         "title": "xxx",
#         .....
#     },
#     {
#
#     }
# ]


# class BookView(View):
#
#     # 第一版 用.values JsonResponse实现序列化
#     # def get(self, request):
#     #     book_list = Book.objects.values("id", "title", "category", "pub_time", "publisher")
#     #     book_list = list(book_list)
#     #     ret = []
#     #     for book in book_list:
#     #         publisher_id = book["publisher"]
#     #         publisher_obj = Publisher.objects.filter(id=publisher_id).first()
#     #         book["publisher"] = {
#     #             "id": publisher_id,
#     #             "title": publisher_obj.title
#     #         }
#     #         ret.append(book)
#     #     # ret = json.dumps(book_list, ensure_ascii=False)
#     #     return JsonResponse(ret, safe=False, json_dumps_params={"ensure_ascii": False})
#
#     # 第二版 用django serializers实现序列化
#     # def get(self, request):
#     #     book_list = Book.objects.all()
#     #     ret = serializers.serialize("json", book_list, ensure_ascii=False)
#     #     return HttpResponse(ret)


class BookView(APIView):

    def get(self, request):
        # book_obj = Book.objects.first()
        # ret = BookSerializer(book_obj)
        book_list = Book.objects.all()
        ret = BookSerializer(book_list, many=True)
        return Response(ret.data)


    def post(self, request):
        print(request.data)
        serializer = BookSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)


class BookEditView(APIView):

    def get(self, request, id):
        book_obj = Book.objects.filter(id=id).first()
        ret = BookSerializer(book_obj)
        return Response(ret.data)
      
    def put(self, request, id):
        book_obj = Book.objects.filter(id=id).first()
        serializer = BookSerializer(book_obj, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        else:
            return Response(serializer.errors)

    def delete(self, request, id):
        book_obj = Book.objects.filter(id=id).first()
        book_obj.delete()
        return Response("")
views.py 
# by gaoxin
from rest_framework import serializers
from .models import Book


class PublisherSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    title = serializers.CharField(max_length=32)


class AuthorSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField(max_length=32)


book_obj = {
        "title": "Alex的使用教程",
        "w_category": 1,
        "pub_time": "2018-10-09",
        "publisher_id": 1,
        "author_list": [1, 2]
    }


data = {
    "title": "Alex的使用教程2"
}


def my_validate(value):
    if "敏感信息" in value.lower():
        raise serializers.ValidationError("不能含有敏感信息")
    else:
        return value


# class BookSerializer(serializers.Serializer):
#     id = serializers.IntegerField(required=False)
#     title = serializers.CharField(max_length=32, validators=[my_validate])
#     CHOICES = ((1, "Python"), (2, "Go"), (3, "Linux"))
#     category = serializers.ChoiceField(choices=CHOICES, source="get_category_display", read_only=True)
#     w_category = serializers.ChoiceField(choices=CHOICES, write_only=True)
#     pub_time = serializers.DateField()
#
#     publisher = PublisherSerializer(read_only=True)
#     publisher_id = serializers.IntegerField(write_only=True)
#     author = AuthorSerializer(many=True, read_only=True)
#     author_list = serializers.ListField(write_only=True)
#
#     def create(self, validated_data):
#         book = Book.objects.create(title=validated_data["title"], category=validated_data["w_category"],
#                                 pub_time=validated_data["pub_time"], publisher_id=validated_data["publisher_id"])
#         book.author.add(*validated_data["author_list"])
#         return book
#
#     def update(self, instance, validated_data):
#         instance.title = validated_data.get("title", instance.title)
#         instance.category = validated_data.get("category", instance.category)
#         instance.pub_time = validated_data.get("pub_time", instance.pub_time)
#         instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
#         if validated_data.get("author_list"):
#             instance.author.set(validated_data["author_list"])
#         instance.save()
#         return instance
#
#     def validate_title(self, value):
#         if "python" not in value.lower():
#             raise serializers.ValidationError("标题必须含有python")
#         return value
#
#     def validate(self, attrs):
#         if attrs["w_category"] == 1 and attrs["publisher_id"] == 1:
#             return attrs
#         else:
#             raise serializers.ValidationError("分类以及标题不符合要求")


class BookSerializer(serializers.ModelSerializer):
    category_display = serializers.SerializerMethodField(read_only=True)
    publisher_info = serializers.SerializerMethodField(read_only=True)
    authors = serializers.SerializerMethodField(read_only=True)

    def get_category_display(self, obj):
        return obj.get_category_display()

    def get_authors(self, obj):
        authors_query_set = obj.author.all()
        return [{"id": author_obj.id, "name": author_obj.name} for author_obj in authors_query_set]

    def get_publisher_info(self, obj):
        # obj 是我们序列化的每个Book对象
        publisher_obj = obj.publisher
        return {"id": publisher_obj.id, "title": publisher_obj.title}

    class Meta:
        model = Book
        # fields = ["id", "title", "pub_time"]
        fields = "__all__"
        # depth = 1 #根据外键关系向下找一层(id-->对象)
        extra_kwargs = {"category": {"write_only": True}, "publisher": {"write_only": True},
                        "author": {"write_only": True}}
serializers.py

4. 视图

from django.shortcuts import render
from django.views import View
from django.http import HttpResponse, JsonResponse
from django.core import serializers
from .models import Book, Publisher
import json
# from rest_framework.generics import ListCreateAPIView,RetrieveUpdateDestroyAPIView


from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework.response import Response
from .serializers import BookSerializer



# class GenericAPIView(APIView):
#     query_set = None
#     serializer_class = None
#
#     def get_queryset(self):
#         return self.query_set
#
#     def get_serializer(self, *args, **kwargs):
#         return self.serializer_class(*args, **kwargs)
#
#
# class ListModelMixin(object):
#     def list(self, request):
#         queryset = self.get_queryset()
#         ret = self.get_serializer(queryset, many=True)
#         return Response(ret.data)
#
#
# class CreateModelMixin(object):
#     def create(self, request):
#         serializer = self.get_serializer(data=request.data)
#         if serializer.is_valid():
#             serializer.save()
#             return Response(serializer.data)
#         else:
#             return Response(serializer.errors)
#
#
# class RetrieveModelMixin(object):
#     def retrieve(self, request, id):
#         book_obj = self.get_queryset().filter(id=id).first()
#         ret = self.get_serializer(book_obj)
#         return Response(ret.data)
#
#
# class UpdateModelMixin(object):
#     def update(self, request, id):
#         book_obj = self.get_queryset().filter(id=id).first()
#         serializer = self.get_serializer(book_obj, data=request.data, partial=True)
#         if serializer.is_valid():
#             serializer.save()
#             return Response(serializer.data)
#         else:
#             return Response(serializer.errors)
#
#
# class DestroyModelMixin(object):
#     def destroy(self, request, id):
#         book_obj = self.get_queryset().filter(id=id).first()
#         book_obj.delete()
#         return Response("")
#
#
# class ListCreateAPIView(GenericAPIView, ListModelMixin, CreateModelMixin):
#     pass
#
#
# class RetrieveUpdateDestroyAPIView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
#     pass


# class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):

class BookView(ListCreateAPIView):
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request):
        # book_obj = Book.objects.first()
        # ret = BookSerializer(book_obj)
        # book_list = Book.objects.all()
        # book_list = self.get_queryset()
        # ret = self.get_serializer(book_list, many=True)
        # return Response(ret.data)
        return self.list(request)

    def post(self, request):
        # print(request.data)
        # serializer = BookSerializer(data=request.data)
        # if serializer.is_valid():
        #     serializer.save()
        #     return Response(serializer.data)
        # else:
        #     return Response(serializer.errors)
        return self.create(request)


# class BookEditView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
class BookEditView(RetrieveUpdateDestroyAPIView):
    query_set = Book.objects.all()
    serializer_class = BookSerializer

    def get(self, request, id):
        # book_obj = Book.objects.filter(id=id).first()
        # ret = BookSerializer(book_obj)
        # return Response(ret.data)
        return self.retrieve(request, id)

    def put(self, request, id):
        # book_obj = Book.objects.filter(id=id).first()
        # serializer = BookSerializer(book_obj, data=request.data, partial=True)
        # if serializer.is_valid():
        #     serializer.save()
        #     return Response(serializer.data)
        # else:
        #     return Response(serializer.errors)
        return self.update(request, id)

    def delete(self, request, id):
        # book_obj = Book.objects.filter(id=id).first()
        # book_obj.delete()
        # return Response("")
        return self.destroy(request, id)
封装原理解析
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import CreateAPIView, ListAPIView,RetrieveAPIView
from rest_framework import status
from api import models

from django.forms import model_to_dict
from django.db.models import F

from utils.auth import GeneralAuthentication,UserAuthentication


# ListAPIView
class NewsView(ListAPIView):
    serializer_class = NewsModelSerializer
    queryset = models.News.objects.all().order_by('-id')

    pagination_class = OldBoyLimitPagination
    filter_backends = [MinFilterBackend, MaxFilterBackend]


#RetrieveAPIView
class NewsDetailView(RetrieveAPIView):
    queryset = models.News.objects
    serializer_class = NewsDetailModelSerializer

    def get(self,request, *args,**kwargs):

        response = super().get(request, *args,**kwargs)
        if not request.user:
            return response
        # 判断当前用户是否有访问此新闻的记录?
        news_object = self.get_object() # models.News.objects.get(pk=pk)
        exists = models.ViewerRecord.objects.filter(user=request.user,news=news_object).exists()
        if exists:
            return response
        models.ViewerRecord.objects.create(user=request.user,news=news_object)
        models.News.objects.filter(id=news_object.id).update(viewer_count=F('viewer_count')+1)

        return response


class TopicView(ListAPIView):
    serializer_class = TopicModelSerializer
    queryset = models.Topic.objects.all().order_by('-id')

    pagination_class = OldBoyLimitPagination
    filter_backends = [MinFilterBackend, MaxFilterBackend]


#APIView
class CommentView(APIView):

    def get_authenticators(self):
        if self.request.method == 'POST':
            return [UserAuthentication(), ]
        return [GeneralAuthentication(), ]

    def get(self,request,*args,**kwargs):
        root_id = request.query_params.get('root')
        # 1. 获取这个根评论的所有子孙评论
        node_queryset = models.CommentRecord.objects.filter(root_id=root_id).order_by('id')
        # 2. 序列化
        ser = CommentModelSerializer(instance=node_queryset,many=True)

        return Response(ser.data,status=status.HTTP_200_OK)

    def post(self,request,*args,**kwargs):
        # 1. 进行数据校验: news/depth/reply/content/root
        ser = CreateCommentModelSerializer(data=request.data)
        if ser.is_valid():
            # 保存到数据库
            ser.save(user_id=1)
            # 对新增到的数据值进行序列化(数据格式需要调整)
            news_id = ser.data.get('news')
            models.News.objects.filter(id=news_id).update(comment_count=F('comment_count')+1)

            return Response(ser.data,status=status.HTTP_201_CREATED)
        return Response(ser.errors,status=status.HTTP_400_BAD_REQUEST)

class FavorView(APIView):
    authentication_classes = [UserAuthentication,]
    def post(self,request,*args,**kwargs):
        ser = FavorModelSerializer(data=request.data)
        if not ser.is_valid():
            return Response({},status=status.HTTP_400_BAD_REQUEST)
        news_object = ser.validated_data.get('news')
        queryset = models.NewsFavorRecord.objects.filter(user=request.user,news=news_object)
        exists = queryset.exists()
        if exists:
            queryset.delete()
            return Response({},status=status.HTTP_200_OK)
        models.NewsFavorRecord.objects.create(user=request.user,news=news_object)
        return Response({},status=status.HTTP_201_CREATED)


class TestView(ListAPIView):
    queryset = models.Topic.objects
    serializer_class = TestSER





class TestView(ListAPIView,CreateAPIView):
    queryset = models.Topic.objects
    serializer_class = TestSER
使用展示(参考使用方法)

5. 路由组件

6. 版本控制

from django.urls import path, include
from .views import DemoView


urlpatterns = [
    path(r"", DemoView.as_view()),
]
urls.py
from django.shortcuts import render

from rest_framework.views import APIView
from rest_framework.response import Response


class DemoView(APIView):
    def get(self, request):
        print(request.version)
        print(request.versioning_scheme)
        # 得到版本号  根据版本号的不同返回不同的信息
        if request.version == "v1":
            return Response("v1版本的数据")
        elif request.version == "v2":
            return Response("v2版本的数据")
        return Response("不存在的版本")
views.py
REST_FRAMEWORK = {
    # "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion",
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",
    "DEFAULT_VERSION": "v1",
    "ALLOWED_VERSIONS": "v1, v2",
    "VERSION_PARAM": "ver"
}
settings.py
from rest_framework import versioning


class MyVersion(object):
    def determine_version(self, request, *args, **kwargs):
        # 返回值 给了request.version
        # 返回版本号
        # 版本号携带在过滤条件 xxxx?version=v1
        version = request.query_params.get("version", "v1")

        return version
utils/version.py

7. 认证组件

from django.db import models

# Create your models here.


class User(models.Model):

    username = models.CharField(max_length=32)
    pwd = models.CharField(max_length=16)
    token = models.UUIDField()
models.py
from django.urls import path
from .views import DemoView, LoginView, TestView


urlpatterns = [
    path(r"", DemoView.as_view()),
    path(r"login", LoginView.as_view()),
    path(r"test", TestView.as_view()),


]
urls.py
from django.shortcuts import render
import uuid
from .models import User
from utils.auth import MyAuth
# Create your views here.

from rest_framework.views import APIView
from rest_framework.response import Response


class DemoView(APIView):
    def get(self, request):
        return Response("认证demo~")


class LoginView(APIView):

    def post(self, request):
        username = request.data.get("username")
        pwd = request.data.get("pwd")
        # 登录成功 生成token 会把token给你返回
        token = uuid.uuid4()
        User.objects.create(username=username, pwd=pwd, token=token)
        return Response("创建用户成功")


class TestView(APIView):
    authentication_classes = [MyAuth,]

    def get(self, request):
        print(request.user)
        print(request.auth)
        user_id = request.user.id
        return Response("认证测试")
views.py
class MyAuth(BaseAuthentication):

    def authenticate(self, request):
        # 做认证 看他是否登录
        # 从url过滤条件里拿到token
        # 去数据库看token是否合法
        # 合法的token能够获取用户信息
        token = request.query_params.get("token", "")
        if not token:
            raise AuthenticationFailed("没有携带token")
        user_obj = User.objects.filter(token=token).first()
        if not user_obj:
            raise AuthenticationFailed("token不合法")
        # return (None, None)
        return (user_obj, token)
auth.py
REST_FRAMEWORK = {
  
     "DEFAULT_AUTHENTICATION_CLASSES": ["utils.auth.MyAuth", ]
}
settings.py

8. 权限

from django.urls import path
from .views import DemoView, LoginView, TestView


urlpatterns = [
    path(r"", DemoView.as_view()),
    path(r"login", LoginView.as_view()),
    path(r"test", TestView.as_view()),


]
urls.py
from django.shortcuts import render
import uuid
from .models import User
from utils.auth import MyAuth
from utils.permission import MyPermission
# Create your views here.

from rest_framework.views import APIView
from rest_framework.response import Response


class DemoView(APIView):
    def get(self, request):
        return Response("认证demo~")


class LoginView(APIView):

    def post(self, request):
        username = request.data.get("username")
        pwd = request.data.get("pwd")
        # 登录成功 生成token 会把token给你返回
        token = uuid.uuid4()
        User.objects.create(username=username, pwd=pwd, token=token)
        return Response("创建用户成功")


class TestView(APIView):
    authentication_classes = [MyAuth,]
    permission_classes = [MyPermission, ]

    def get(self, request):
        print(request.user)
        print(request.auth)
        user_id = request.user.id
        return Response("认证测试")
views.py
from rest_framework.permissions import BasePermission

class MyPermission(BasePermission):
    message = "您没有权限"

    def has_permission(self, request, view):
        # 判断用户是否有权限
        user_obj = request.user
        if user_obj.type == 3:
            return False
        else:
            return True
permission
REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'UNAUTHENTICATED_TOKEN': None,
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "web.utils.TestAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "web.utils.TestPermission",
    ],
}
settings.py

9. 频率

from django.shortcuts import render
import uuid
from .models import User
from utils.auth import MyAuth
from utils.permission import MyPermission
from utils.throttle import MyThrottle
# Create your views here.

from rest_framework.views import APIView
from rest_framework.response import Response


class DemoView(APIView):
    def get(self, request):
        return Response("认证demo~")


class LoginView(APIView):

    def post(self, request):
        username = request.data.get("username")
        pwd = request.data.get("pwd")
        # 登录成功 生成token 会把token给你返回
        token = uuid.uuid4()
        User.objects.create(username=username, pwd=pwd, token=token)
        return Response("创建用户成功")


class TestView(APIView):
    authentication_classes = [MyAuth,]
    permission_classes = [MyPermission, ]
    throttle_classes = [MyThrottle, ]

    def get(self, request):
        print(request.user)
        print(request.auth)
        user_id = request.user.id
        return Response("认证测试")
views.py
# by gaoxin
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle
import time

VISIT_RECORD = {}


# class MyThrottle(BaseThrottle):
#
#     def __init__(self):
#         self.history = None
#
#     def allow_request(self, request, view):
#         # 实现限流的逻辑
#         # 以IP限流
#         # 访问列表 {IP: [time1, time2, time3]}
#         # 1, 获取请求的IP地址
#         ip = request.META.get("REMOTE_ADDR")
#         # 2,判断IP地址是否在访问列表
#         now = time.time()
#         if ip not in VISIT_RECORD:
#             # --1, 不在 需要给访问列表添加key,value
#             VISIT_RECORD[ip] = [now,]
#             return True
#             # --2 在 需要把这个IP的访问记录 把当前时间加入到列表
#         history = VISIT_RECORD[ip]
#         history.insert(0, now)
#         # 3, 确保列表里最新访问时间以及最老的访问时间差 是1分钟
#         while history and history[0] - history[-1] > 60:
#             history.pop()
#         self.history = history
#         # 4,得到列表长度,判断是否是允许的次数
#         if len(history) > 3:
#             return False
#         else:
#             return True
#
#     def wait(self):
#         # 返回需要再等多久才能访问
#         time = 60 - (self.history[0] - self.history[-1])
#         return time


class MyThrottle(SimpleRateThrottle):
    scope = "WD"

    def get_cache_key(self, request, view):
        # 如果以IP地址做限流返回IP地址
        key = self.get_ident(request)
        return key
throttle.py
REST_FRAMEWORK = {
    # "DEFAULT_VERSIONING_CLASS": "utils.version.MyVersion",
    "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning",
    "DEFAULT_VERSION": "v1",
    "ALLOWED_VERSIONS": "v1, v2",
    "VERSION_PARAM": "ver",
    # "DEFAULT_AUTHENTICATION_CLASSES": ["utils.auth.MyAuth", ],
    "DEFAULT_THROTTLE_RATES": {
        "WD": "3/m"
    }
}
settings.py

10. 分页

from django.urls import path
from .views import BookView

urlpatterns = [
    path('book', BookView.as_view()),

]
urls.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from SerDemo.models import Book
from SerDemo.serializers import BookSerializer

# Create your views here.
from rest_framework import pagination
from utils.pagination import MyPagination
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin


# class BookView(APIView):
#
#     def get(self, request):
#         queryset = Book.objects.all()
#         # 1,实例化分页器对象
#         page_obj = MyPagination()
#         # 2,调用分页方法去分页queryset
#         page_queryset = page_obj.paginate_queryset(queryset, request, view=self)
#         # 3,把分页好的数据序列化返回
#         # 4, 带着上一页下一页连接的响应
#         ser_obj = BookSerializer(page_queryset, many=True)
#
#         return page_obj.get_paginated_response(ser_obj.data)


class BookView(GenericAPIView, ListModelMixin):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    pagination_class = MyPagination
    # self.paginate_queryset(queryset)

    def get(self, request):
        return self.list(request)
views.py
# by gaoxin
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


# class MyPagination(PageNumberPagination):
#     # xxxx?page=1&size=2
#     page_size = 1
#     page_query_param = "page"
#     page_size_query_param = "size"
#     max_page_size = 3



# class MyPagination(LimitOffsetPagination):
#
#     default_limit = 1
#     limit_query_param = "limit"
#     offset_query_param = "offset"
#     max_limit = 3



class MyPagination(CursorPagination):

    cursor_query_param = "cursor"
    page_size = 2
    ordering = "-id"
pagination.py

11. 解析器

from django.shortcuts import render
from django.views import View
from django.http import HttpResponse
from django.core.handlers.wsgi import WSGIRequest
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.negotiation import DefaultContentNegotiation
from rest_framework import parsers

# Create your views here.


class DjangoView(View):
    def get(self, request):
        print(type(request))
        # Request
        # request.GET
        # request.POST
        # json request.body
        return HttpResponse("django解析器测试~~")


class DRFView(APIView):
    parser_classes = [parsers.JSONParser, ]

    def get(self, request):
        # request 重新封装的request  Request
        # request.data
        #
        return Response("DRF解析器的测试~~")
View.py

12. 渲染器

注册 rest_framework

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'yuqing',
]

根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件

json

访问URL:

  • http://127.0.0.1:8000/test/?format=json
  • http://127.0.0.1:8000/test.json
  • http://127.0.0.1:8000/test/ 
from django.conf.urls import url, include
from web.views import s11_render

urlpatterns = [
    url(r'^test/$', s11_render.TestView.as_view()),
    url(r'^test.(?P<format>[a-z0-9]+)', s11_render.TestView.as_view()),
]
urls.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers

from rest_framework.renderers import JSONRenderer

from .. import models


class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class TestView(APIView):
    renderer_classes = [JSONRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all()
        ser = TestSerializer(instance=user_list, many=True)
        return Response(ser.data)
View.py

13. 跨域

设置中间件

from django.middleware.security import SecurityMiddleware
from django.utils.deprecation import MiddlewareMixin


class MyCors(MiddlewareMixin):

    def process_response(self, request, response):
        response["Access-Control-Allow-Origin"] = "*"
        if request.method == "OPTIONS":
            response["Access-Control-Allow-Methods"] = "PUT, DELETE"
            response["Access-Control-Allow-Headers"] = "content-type"
        return response
middlewares.py

14. ContentType

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation

# Create your models here.


class Food(models.Model):
    """
    id      title
    1       面包
    2       牛奶
    """
    title = models.CharField(max_length=32)
    # 不会生成字段 只用于反向查询
    coupons = GenericRelation(to="Coupon")


class Fruit(models.Model):
    """
    id      title
    1       苹果
    2       香蕉
    """
    title = models.CharField(max_length=32)


# 如果有40张表
# class Coupon(models.Model):
#     """
#     id      title          food_id    fruit_id
#     1       面包九五折         1         null
#     2       香蕉满10元减5元    null       2
#     """
#     title = models.CharField(max_length=32)
#     food = models.ForeignKey(to="Food")
#     fruit = models.ForeignKey(to="Fruit")


# class Coupon(models.Model):
#     """
#     id      title        table_id      object_id
#     1       面包九五折       1             1
#     2       香蕉满10元减5元  2             2
#     """
#     title = models.CharField(max_length=32)
#     table = models.ForeignKey(to="Table")
#     object_id = models.IntegerField()
#
#
# class Table(models.Model):
#     """
#     id      app_name       table_name
#     1       demo            food
#     2       demo            fruit
#     """
#     app_name = models.CharField(max_length=32)
#     table_name = models.CharField(max_length=32)


class Coupon(models.Model):
    title = models.CharField(max_length=32)
    # 第一步
    content_type = models.ForeignKey(to=ContentType, on_delete=None)
    # 第二步
    object_id = models.IntegerField()
    # 第三步 不会生成字段
    content_object = GenericForeignKey("content_type", "object_id")
models.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Food, Coupon
from django.contrib.contenttypes.models import ContentType

# Create your views here.


class DemoView(APIView):

    def get(self, request):
        # 给面包创建一个优惠券
        food_obj = Food.objects.filter(id=1).first()
        # Coupon.objects.create(title="面包九五折", content_type_id=8, object_id=1)
        # Coupon.objects.create(title="双十一面包九折促销", content_object=food_obj)

        #查询面包都有哪些优惠券
        coupons = food_obj.coupons.all()
        print(coupons)
        # 优惠券查对象
        coupon_obj = Coupon.objects.filter(id=1).first()
        content_obj = coupon_obj.content_object
        print(coupon_obj.title)

        # 通过ContentType表找表模型
        content = ContentType.objects.filter(app_label="demo", model="food").first()
        print(content)
        model_class = content.model_class()
        ret = model_class.objects.all()
        print(ret)

        return Response("ContentType测试")
View.py

13. 项目

链接(点击查看)

 LuffyCity

from django.middleware.security import SecurityMiddleware

from django.utils.deprecation import MiddlewareMixin


class MyCors(MiddlewareMixin):
    def process_response(self, request, response):
        response["Access-Control-Allow-Origin"] = "*"
        if request.method == "OPTIONS":
            response["Access-Control-Allow-Headers"] = "content-type"
        return response
项目/middlewares.py
"""
Django settings for LuffyCity project.

Generated by 'django-admin startproject' using Django 2.0.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'bs9_-40a4x9i^8^wbc$a0u!n=dkxb%342sh@7*gs5^21bwon1v'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'Course.apps.CourseConfig',
    'Login',
    'shopping',
]

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',
    'middlewares.MyCors'
]

ROOT_URLCONF = 'LuffyCity.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'LuffyCity.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',
        'PORT': '3306',
        'USER': 'root',
        'PASSWORD': 'root1234',
        'NAME': 'luffycity',
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'

# Media配置
MEDIA_URL = "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")


VIDEO_CONFIG = {
    "POLYV": {
        "USER_ID": "03b56854c0",
        "SECRET_KEY": "G128dqgzTp",
    }
}
项目/项目/settings.py
app/admin.py
from django.db import models
# Create your models here.

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.contrib.contenttypes.models import ContentType

# Create your models here.
__all__ = ["Category", "Course", "CourseDetail", "Teacher", "DegreeCourse", "CourseChapter",
           "CourseSection", "PricePolicy", "OftenAskedQuestion", "Comment", "Account", "CourseOutline"]


class Category(models.Model):
    """课程分类表"""
    title = models.CharField(max_length=32, unique=True, verbose_name="课程的分类")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "01-课程分类表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Course(models.Model):
    """课程表"""
    title = models.CharField(max_length=128, unique=True, verbose_name="课程的名称")
    course_img = models.ImageField(upload_to="course/%Y-%m", verbose_name='课程的图片')
    category = models.ForeignKey(to="Category", verbose_name="课程的分类", on_delete=None)


    COURSE_TYPE_CHOICES = ((0, "付费"), (1, "vip专享"), (2, "学位课程"))
    course_type = models.SmallIntegerField(choices=COURSE_TYPE_CHOICES)
    degree_course = models.ForeignKey(to="DegreeCourse", blank=True, null=True, help_text="如果是学位课程,必须关联学位表", on_delete=None)


    brief = models.CharField(verbose_name="课程简介", max_length=1024)
    level_choices = ((0, '初级'), (1, '中级'), (2, '高级'))
    level = models.SmallIntegerField(choices=level_choices, default=1)

    status_choices = ((0, '上线'), (1, '下线'), (2, '预上线'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)

    order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
    study_num = models.IntegerField(verbose_name="学习人数", help_text="只要有人买课程,订单表加入数据的同时给这个字段+1")

    # order_details = GenericRelation("OrderDetail", related_query_name="course")
    # coupon = GenericRelation("Coupon")
    # 只用于反向查询不生成字段
    price_policy = GenericRelation("PricePolicy")
    often_ask_questions = GenericRelation("OftenAskedQuestion")
    course_comments = GenericRelation("Comment")

    def save(self, *args, **kwargs):
        if self.course_type == 2:
            if not self.degree_course:
                raise ValueError("学位课必须关联学位课程表")
        super(Course, self).save(*args, **kwargs)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "02-课程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseDetail(models.Model):
    """课程详细表"""
    course = models.OneToOneField(to="Course", on_delete=None)
    hours = models.IntegerField(verbose_name="课时", default=7)
    course_slogan = models.CharField(max_length=125, blank=True, null=True, verbose_name="课程口号")
    video_brief_link = models.CharField(max_length=255, blank=True, null=True)
    summary = models.TextField(max_length=2048, verbose_name="课程概述")
    why_study = models.TextField(verbose_name="为什么学习这门课程")
    what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
    career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
    prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
    recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)

    teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")

    def __str__(self):
        return self.course.title

    class Meta:
        verbose_name = "03-课程详细表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Teacher(models.Model):
    """讲师表"""
    name = models.CharField(max_length=32, verbose_name="讲师名字")
    brief = models.TextField(max_length=1024, verbose_name="讲师介绍")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "04-教师表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class DegreeCourse(models.Model):
    """
    字段大体跟课程表相同,哪些不同根据业务逻辑去区分
    """
    title = models.CharField(max_length=32, verbose_name="学位课程名字")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "05-学位课程表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseChapter(models.Model):
    """课程章节表"""
    course = models.ForeignKey(to="Course", related_name="course_chapters", on_delete=None)
    chapter = models.SmallIntegerField(default=1, verbose_name="第几章")
    title = models.CharField(max_length=32, verbose_name="课程章节名称")

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "06-课程章节表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("course", "chapter")


class CourseSection(models.Model):
    """课时表"""
    chapter = models.ForeignKey(to="CourseChapter", related_name="course_sections", on_delete=None)
    title = models.CharField(max_length=32, verbose_name="课时")
    section_order = models.SmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
    section_type_choices = ((0, '文档'), (1, '练习'), (2, '视频'))
    free_trail = models.BooleanField("是否可试看", default=False)
    section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
    section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")

    def course_chapter(self):
        return self.chapter.chapter

    def course_name(self):
        return self.chapter.course.title

    def __str__(self):
        return "%s-%s" % (self.chapter, self.title)

    class Meta:
        verbose_name = "07-课程课时表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('chapter', 'section_link')


class PricePolicy(models.Model):
    """价格策略表"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    valid_period_choices = ((1, '1天'), (3, '3天'),
                            (7, '1周'), (14, '2周'),
                            (30, '1个月'),
                            (60, '2个月'),
                            (90, '3个月'),
                            (120, '4个月'),
                            (180, '6个月'), (210, '12个月'),
                            (540, '18个月'), (720, '24个月'),
                            (722, '24个月'), (723, '24个月'),
                            )
    valid_period = models.SmallIntegerField(choices=valid_period_choices)
    price = models.FloatField()

    def __str__(self):
        return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)

    class Meta:
        verbose_name = "08-价格策略表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ("content_type", 'object_id', "valid_period")


class OftenAskedQuestion(models.Model):
    """常见问题"""
    content_type = models.ForeignKey(ContentType, on_delete=None)  # 关联course or degree_course
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

    question = models.CharField(max_length=255)
    answer = models.TextField(max_length=1024)

    def __str__(self):
        return "%s-%s" % (self.content_object, self.question)

    class Meta:
        verbose_name = "09-常见问题表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('content_type', 'object_id', 'question')


class Comment(models.Model):
    """通用的评论表"""
    content_type = models.ForeignKey(ContentType, blank=True, null=True, on_delete=None)
    object_id = models.PositiveIntegerField(blank=True, null=True)
    content_object = GenericForeignKey('content_type', 'object_id')

    content = models.TextField(max_length=1024, verbose_name="评论内容")
    account = models.ForeignKey("Account", verbose_name="会员名", on_delete=None)
    date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    class Meta:
        verbose_name = "10-评价表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class Account(models.Model):
    username = models.CharField(max_length=32, verbose_name="用户姓名")
    pwd = models.CharField(max_length=32, verbose_name="密文密码")
    # head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png',
    #                             verbose_name="个人头像")
    balance = models.IntegerField(verbose_name="贝里余额", default=0)

    def __str__(self):
        return self.username

    class Meta:
        verbose_name = "11-用户表"
        db_table = verbose_name
        verbose_name_plural = verbose_name


class CourseOutline(models.Model):
    """课程大纲"""
    course_detail = models.ForeignKey(to="CourseDetail", related_name="course_outline", on_delete=None)
    title = models.CharField(max_length=128)
    order = models.PositiveSmallIntegerField(default=1)
    # 前端显示顺序

    content = models.TextField("内容", max_length=2048)

    def __str__(self):
        return "%s" % self.title

    class Meta:
        verbose_name = "12-课程大纲表"
        db_table = verbose_name
        verbose_name_plural = verbose_name
        unique_together = ('course_detail', 'title')
app/models.py
from django.contrib import admin
from django.urls import path, include, re_path
from django.views.static import serve
from LuffyCity import settings
from Login.views import GeetestView


urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/course/', include("Course.urls")),
    path('api/shop/', include("shopping.urls")),
    path('api/', include("Login.urls")),
    path('pc-geetest/register', GeetestView.as_view()),
    path('pc-geetest/ajax_validate', GeetestView.as_view()),


    # media路径配置
    # path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
    re_path('media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT})
]
项目/项目/urls.py

utils

class BaseResponse(object):

    def __init__(self):
        self.code = 1000
        self.data = None
        self.error = None


    @property
    def dict(self):
        return self.__dict__
base_response
# by gaoxin
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .redis_pool import POOL
from Course.models import Account
import redis



CONN = redis.Redis(connection_pool=POOL)


class LoginAuth(BaseAuthentication):
    def authenticate(self, request):
        # 从请求头中获取前端带过来的token
        token = request.META.get("HTTP_AUTHENTICATION", "")
        if not token:
            raise AuthenticationFailed("没有携带token")
        # 去redis比对
        user_id = CONN.get(str(token))
        if user_id == None:
            raise AuthenticationFailed("token过期")
        user_obj = Account.objects.filter(id=user_id).first()
        return user_obj, token
my_auth.py
# by gaoxin

import redis


POOL = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True, max_connections=10)
redis_pool

Login(app)

from django.urls import path
from .views import RegisterView, LoginView, TestView


urlpatterns = [
    path('register', RegisterView.as_view()),
    path('login', LoginView.as_view()),
    path('test_auth', TestView.as_view()),

]
urls.py
from rest_framework import serializers
from Course.models import Account
import hashlib


class RegisterSerializer(serializers.ModelSerializer):

    class Meta:
        model = Account
        fields = "__all__"

    def create(self, validated_data):
        pwd = validated_data["pwd"]
        pwd_salt = "luffy_password" + pwd
        md5_str = hashlib.md5(pwd_salt.encode()).hexdigest()
        user_obj = Account.objects.create(username=validated_data["username"], pwd=md5_str)
        return user_obj
serializers.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import RegisterSerializer
from utils.base_response import BaseResponse
from Course.models import Account
from utils.redis_pool import POOL
import redis
import uuid
from utils.my_auth import LoginAuth
from utils.geetest import GeetestLib
from django.http import HttpResponse
import json

# Create your views here.


class RegisterView(APIView):

    def post(self, request):
        res = BaseResponse()
        # 用序列化器做校验
        ser_obj = RegisterSerializer(data=request.data)
        if ser_obj.is_valid():
            ser_obj.save()
            res.data = ser_obj.data
        else:
            res.code = 1020
            res.error = ser_obj.errors
        return Response(res.dict)


class LoginView(APIView):

    def post(self, request):
        res = BaseResponse()
        username = request.data.get("username", "")
        pwd = request.data.get("pwd", "")
        user_obj = Account.objects.filter(username=username, pwd=pwd).first()
        if not user_obj:
            res.code = 1030
            res.error = "用户名或密码错误"
            return Response(res.dict)
        # 用户登录成功生成一个token写入redis
        # 写入redis  token : user_id
        conn = redis.Redis(connection_pool=POOL)
        try:
            token = uuid.uuid4()
            # conn.set(str(token), user_obj.id, ex=10)
            conn.set(str(token), user_obj.id)
            res.data = token
        except Exception as e:
            print(e)
            res.code = 1031
            res.error = "创建令牌失败"
        return Response(res.dict)


class TestView(APIView):
    authentication_classes = [LoginAuth, ]

    def get(self, request):
        return Response("认证测试")


pc_geetest_id = "b46d1900d0a894591916ea94ea91bd2c"
pc_geetest_key = "36fc3fe98530eea08dfc6ce76e3d24c4"
REDIS_CONN = redis.Redis(connection_pool=POOL)


class GeetestView(APIView):

    def get(self, request):
        user_id = 'test'
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        status = gt.pre_process(user_id)
        # request.session[gt.GT_STATUS_SESSION_KEY] = status
        REDIS_CONN.set(gt.GT_STATUS_SESSION_KEY, status)
        # request.session["user_id"] = user_id
        REDIS_CONN.set("gt_user_id", user_id)
        response_str = gt.get_response_str()
        return HttpResponse(response_str)

    def post(self, request):
        # print(request.session.get("user_id"))
        print(request.META.get("HTTP_AUTHENTICATION"))
        print(request.data)
        gt = GeetestLib(pc_geetest_id, pc_geetest_key)
        challenge = request.data.get(gt.FN_CHALLENGE, '')
        validate = request.data.get(gt.FN_VALIDATE, '')
        seccode = request.data.get(gt.FN_SECCODE, '')
        # username
        # pwd
        # status = request.session.get(gt.GT_STATUS_SESSION_KEY)
        # print(status)
        # user_id = request.session.get("user_id")
        # print(user_id)
        status = REDIS_CONN.get(gt.GT_STATUS_SESSION_KEY)
        user_id = REDIS_CONN.get("gt_user_id")
        if status:
            result = gt.success_validate(challenge, validate, seccode, user_id)
        else:
            result = gt.failback_validate(challenge, validate, seccode)
        result = {"status": "success"} if result else {"status": "fail"}
        # if result:
        #     # 证明验证码通过
        #     # 判断用户名和密码
        # else:
        #     #  返回验证码错误
        return HttpResponse(json.dumps(result))
views.py

Course(app)

from django.urls import path
from .views import CategoryView, CourseView, CourseDetailView, CourseChapterView, CourseCommentView, QuestionView
from .video_view import PolyvView


urlpatterns = [
    path('category', CategoryView.as_view()),
    path('list', CourseView.as_view()),
    path('detail/<int:pk>', CourseDetailView.as_view()),
    path('chapter/<int:pk>', CourseChapterView.as_view()),
    path('comment/<int:pk>', CourseCommentView.as_view()),
    path('question/<int:pk>', QuestionView.as_view()),
    path('polyv', PolyvView.as_view()),

]
urls.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from .serializers import CategorySerializer, CourseSerializer, CourseDetailSerializer, CourseChapterSerializer
from .serializers import CourseCommentSerializer, QuestionSerializer

# Create your views here.


class CategoryView(APIView):
    def get(self, request):
        # 通过ORM操作获取所有分类数据
        queryset = models.Category.objects.all()
        # 利用序列化器去序列化我们的数据
        ser_obj = CategorySerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseView(APIView):
    def get(self, request):
        # 获取过滤条件中的分类ID
        category_id = request.query_params.get("category", 0)
        # 根据分类获取课程
        if category_id == 0:
            queryset = models.Course.objects.all().order_by("order")
        else:
            queryset = models.Course.objects.filter(category_id=category_id).all().order_by("order")
        # 序列化课程数据
        ser_obj = CourseSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseDetailView(APIView):
    def get(self, request, pk):
        # 根据pk获取到课程详情对象
        course_detail_obj = models.CourseDetail.objects.filter(course__id=pk).first()
        if not course_detail_obj:
            return Response({"code": 1001, "error": "查询的课程详情不存在"})
        # 序列化课程详情
        ser_obj = CourseDetailSerializer(course_detail_obj)
        # 返回
        return Response(ser_obj.data)


class CourseChapterView(APIView):
    def get(self, request, pk):
        # ["第一章": {课时一, 课时二}]
        queryset = models.CourseChapter.objects.filter(course_id=pk).all().order_by("chapter")
        # 序列化章节对象
        ser_obj = CourseChapterSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class CourseCommentView(APIView):
    def get(self, request, pk):
        # 通过课程id找到课程所有的评论
        queryset = models.Course.objects.filter(id=pk).first().course_comments.all()
        # 序列化
        ser_obj = CourseCommentSerializer(queryset, many=True)
        # 返回
        return Response(ser_obj.data)


class QuestionView(APIView):
    def get(self, request, pk):
        queryset = models.Course.objects.filter(id=pk).first().often_ask_questions.all()
        ser_obj = QuestionSerializer(queryset, many=True)
        return Response(ser_obj.data)
views.py
# by gaoxin
from rest_framework import serializers
from . import models


class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Category
        fields = "__all__"


class CourseSerializer(serializers.ModelSerializer):
    level = serializers.CharField(source="get_level_display")
    price = serializers.SerializerMethodField()

    def get_price(self, obj):
        print(obj.price_policy.all())
        return obj.price_policy.all().order_by("price").first().price

    class Meta:
        model = models.Course
        fields = ["id", "title", "course_img", "brief", "level", "study_num", "price"]


class CourseDetailSerializer(serializers.ModelSerializer):
    level = serializers.CharField(source="course.get_level_display")
    study_num = serializers.IntegerField(source="course.study_num")
    recommend_courses = serializers.SerializerMethodField()
    teachers = serializers.SerializerMethodField()
    price_policy = serializers.SerializerMethodField()
    course_outline = serializers.SerializerMethodField()


    def get_course_outline(self, obj):
        return [{"id": outline.id, "title": outline.title, "content": outline.content} for outline in obj.course_outline.all().order_by("order")]


    def get_price_policy(self, obj):
        return [{"id": price.id, "valid_price_display": price.get_valid_period_display(), "price": price.price} for price in obj.course.price_policy.all()]


    def get_teachers(self, obj):
        return [{"id": teacher.id, "name": teacher.name} for teacher in obj.teachers.all()]

    def get_recommend_courses(self, obj):
        return [{"id": course.id, "title": course.title} for course in obj.recommend_courses.all()]

    class Meta:
        model = models.CourseDetail
        fields = ["id", "hours", "summary", "level", "study_num", "recommend_courses", "teachers",
                  "price_policy", "course_outline"]


class CourseChapterSerializer(serializers.ModelSerializer):
    sections = serializers.SerializerMethodField()

    def get_sections(self, obj):
        return [{"id": section.id, "title": section.title, "free_trail": section.free_trail} for section in obj.course_sections.all().order_by("section_order")]

    class Meta:
        model = models.CourseChapter
        fields = ["id", "title", "sections"]


class CourseCommentSerializer(serializers.ModelSerializer):
    account = serializers.CharField(source="account.username")

    class Meta:
        model = models.Comment
        fields = ["id", "account", "content", "date"]


class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.OftenAskedQuestion
        fields = ["id", "question", "answer"]
serializers.py

Shopping(app)

from django.urls import path
from .views import ShoppingCarView
from .settlement_view import SettlementView
from .payment_view import PaymentView

urlpatterns = [
    path('shopping_car', ShoppingCarView.as_view()),
    path('settlement', SettlementView.as_view()),
    path('payment', PaymentView.as_view()),


]
urls.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from utils.base_response import BaseResponse
from utils.my_auth import LoginAuth
from utils.redis_pool import POOL
from Course.models import Course
import json
import redis

# Create your views here.

# 前端传过来 course_id  price_policy_id
# 把购物车数据放入redis
"""
{
    SHOPPINGCAR_USERID_COURSE_ID: {
        "id", 
        "title",
        "course_img",
        "price_policy_dict": {
            price_policy_id: "{valid_period,  price}"
            price_policy_id2: "{valid_period,  price}"
            price_policy_id3: "{valid_period,  price}"
        
        },
        "default_price_policy_id": 1
        
    
    }


}
"""

SHOPPINGCAR_KEY = "SHOPPINGCAR_%s_%s"
CONN = redis.Redis(connection_pool=POOL)


class ShoppingCarView(APIView):
    authentication_classes = [LoginAuth, ]

    def post(self, request):
        res = BaseResponse()
        # 1, 获取前端传过来的数据以及user_id
        course_id = request.data.get("course_id", "")
        price_policy_id = request.data.get("price_policy_id", "")
        user_id = request.user.pk
        # 2, 校验数据的合法性
        # 2.1 校验课程id合法性
        course_obj = Course.objects.filter(id=course_id).first()
        if not course_obj:
            res.code = 1040
            res.error = "课程id不合法"
            return Response(res.dict)
        # 2.2 校验价格策略id是否合法
        price_policy_queryset = course_obj.price_policy.all()
        price_policy_dict = {}
        for price_policy in price_policy_queryset:
            price_policy_dict[price_policy.id] = {
                "price": price_policy.price,
                "valid_period": price_policy.valid_period,
                "valid_period_display": price_policy.get_valid_period_display()
            }
        if price_policy_id not in price_policy_dict:
            res.code = 1041
            res.error = "价格策略id不合法"
            return Response(res.dict)
        # 3,构建redisKEY
        key = SHOPPINGCAR_KEY % (user_id, course_id)
        # 4,构建数据结构
        course_info = {
            "id": course_obj.id,
            "title": course_obj.title,
            "course_img": str(course_obj.course_img),
            "price_policy_dict": json.dumps(price_policy_dict, ensure_ascii=False),
            "default_price_policy_id": price_policy_id
        }
        # 5  写入redis
        # CONN.hmset(key, course_info)
        CONN.hset(key, mapping=course_info) # 推荐
        res.data = "加入购物车成功"
        return Response(res.dict)

    def get(self, request):
        res = BaseResponse()
        # 1, 拼接redis key
        user_id = request.user.pk
        shopping_car_key = SHOPPINGCAR_KEY % (user_id, "*")
        # 2, 去redis中读取数据
        # 2.1 匹配所有的keys
        # 3,构建数据结构展示
        all_keys = CONN.scan_iter(shopping_car_key)
        ret = []
        for key in all_keys:
            ret.append(CONN.hgetall(key))
        res.data = ret
        return Response(res.dict)

    def put(self, request):
        # 前端 course_id  price_policy_id
        res = BaseResponse()
        # 1, 获取前端传过来的数据以及user_id
        course_id = request.data.get("course_id", "")
        price_policy_id = request.data.get("price_policy_id", "")
        user_id = request.user.pk
        # 2, 校验数据的合法性
        # 2.1 course_id是否合法
        key = SHOPPINGCAR_KEY % (user_id, course_id)
        if not CONN.exists(key):
            res.code = 1043
            res.error = "课程id不合法"
            return Response(res.dict)
        # 2,2 price_policy_id是否合法
        price_policy_dict = json.loads(CONN.hget(key, "price_policy_dict"))
        if str(price_policy_id) not in price_policy_dict:
            res.code = 1044
            res.error = "价格策略不合法"
            return Response(res.dict)
        # 3, 更新redis  default_price_policy_id
        CONN.hset(key, "default_price_policy_id", price_policy_id)
        res.data = "更新成功"
        return Response(res.dict)

    def delete(self, request):
        # course_list = [course_id, ]
        res = BaseResponse()
        # 1 获取前端传来的数据以及user_id
        course_list = request.data.get("course_list", "")
        user_id = request.user.pk
        # 2 校验course_id是否合法
        for course_id in course_list:
            key = SHOPPINGCAR_KEY % (user_id, course_id)
            if not CONN.exists(key):
                res.code = 1045
                res.error = "课程ID不合法"
                return Response(res.dict)
            # 3, 删除redis数据
            CONN.delete(key)
        res.data = "删除成功"
        return Response(res.dict)
views.py

小程序后端

from django.conf.urls import url,include

from django.contrib import admin
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/', include('api.urls')),
]
项目/urls.py

api(app)

from django.conf.urls import url

from api.views import news
from api.views import auth
urlpatterns = [
    url(r'^message/', auth.MessageView.as_view()),
    url(r'^login/', auth.LoginView.as_view()),


    url(r'^topic/$', news.TopicView.as_view()),
    url(r'^news/$', news.NewsView.as_view()),
    url(r'^news/(?P<pk>d+)/$', news.NewsDetailView.as_view()),
    url(r'^comment/$', news.CommentView.as_view()),
    url(r'^favor/$', news.FavorView.as_view()),

    url(r'^test/$', news.TestView.as_view()),

]
urls.py
from django.db import models


class UserInfo(models.Model):
    telephone = models.CharField(verbose_name='手机号', max_length=11)
    nickname = models.CharField(verbose_name='昵称', max_length=64)
    avatar = models.CharField(verbose_name='头像', max_length=64)
    token = models.CharField(verbose_name='用户Token', max_length=64,null=True,blank=True)


class Topic(models.Model):
    """
    话题
    """
    title = models.CharField(verbose_name='话题', max_length=32)
    count = models.PositiveIntegerField(verbose_name='关注度', default=0)


class News(models.Model):
    """
    动态
    """
    cover = models.CharField(verbose_name='封面', max_length=128)
    content = models.CharField(verbose_name='内容', max_length=255)
    topic = models.ForeignKey(verbose_name='话题', to='Topic', null=True, blank=True)
    address = models.CharField(verbose_name='位置', max_length=128, null=True, blank=True)
    user = models.ForeignKey(verbose_name='发布者', to='UserInfo', related_name='news')

    favor_count = models.PositiveIntegerField(verbose_name='赞数', default=0)

    viewer_count = models.PositiveIntegerField(verbose_name='浏览数', default=0)

    comment_count = models.PositiveIntegerField(verbose_name='评论数', default=0)

    create_date = models.DateTimeField(verbose_name='创建时间', auto_now_add=True)

class NewsDetail(models.Model):
    """
    动态详细
    """
    key = models.CharField(verbose_name='腾讯对象存储中的文件名', max_length=128, help_text="用于以后在腾讯对象存储中删除")
    cos_path = models.CharField(verbose_name='腾讯对象存储中图片路径', max_length=128)
    news = models.ForeignKey(verbose_name='动态', to='News')


class ViewerRecord(models.Model):
    """
    浏览记录
    """
    news = models.ForeignKey(verbose_name='动态', to='News')
    user = models.ForeignKey(verbose_name='用户', to='UserInfo')


class NewsFavorRecord(models.Model):
    """
    动态赞记录表
    """
    news = models.ForeignKey(verbose_name='动态', to='News')
    user = models.ForeignKey(verbose_name='点赞用户', to='UserInfo')


class CommentRecord(models.Model):
    """
    评论记录表
    """
    news = models.ForeignKey(verbose_name='动态', to='News')
    content = models.CharField(verbose_name='评论内容', max_length=255)
    user = models.ForeignKey(verbose_name='评论者', to='UserInfo')
    create_date = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)

    reply = models.ForeignKey(verbose_name='回复', to='self', null=True, blank=True,related_name='replys')
    depth = models.PositiveIntegerField(verbose_name='评论层级', default=1)
    root = models.ForeignKey(verbose_name='根评论',to='self',null=True,blank=True,related_name="roots")

    favor_count = models.PositiveIntegerField(verbose_name='赞数', default=0)


class CommentFavorRecord(models.Model):
    """
    评论赞记录
    """
    comment = models.ForeignKey(verbose_name='动态', to='CommentRecord')
    user = models.ForeignKey(verbose_name='点赞用户', to='UserInfo')
models.py

serilalizer

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from django_redis import get_redis_connection

from .validators import phone_validator

class MessageSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号',validators=[phone_validator,])


class LoginSerializer(serializers.Serializer):
    phone = serializers.CharField(label='手机号', validators=[phone_validator, ])
    code = serializers.CharField(label='短信验证码')
    nickname = serializers.CharField(label='昵称')
    avatar = serializers.CharField(label='头像')

    def validate_code(self, value):
        if len(value) !=4:
            raise ValidationError('短信格式错误')
        if not value.isdecimal():
            raise ValidationError('短信格式错误')

        phone = self.initial_data.get('phone')
        conn = get_redis_connection()
        code = conn.get(phone)
        if not code:
            raise ValidationError('验证码过期')
        if value != code.decode('utf-8'):
            raise ValidationError('验证码错误')

        return value
account.py
import re
from rest_framework.exceptions import ValidationError


def phone_validator(value):
    if not re.match(r"^(1[3|4|5|6|7|8|9])d{9}$",value):
        raise ValidationError('手机格式错误')
validators.py

views

import re
import random
import uuid

from rest_framework.views import APIView
from rest_framework.response import Response
from django_redis import get_redis_connection

from api import models
from utils.tencent.msg import send_message
from api.serializer.account import MessageSerializer, LoginSerializer


class MessageView(APIView):
    """
    发送短信接口
    """

    def get(self, request, *args, **kwargs):
        ser = MessageSerializer(data=request.query_params)
        if not ser.is_valid():
            return Response({'status': False, 'message': '手机格式错误'})
        phone = ser.validated_data.get('phone')
        random_code = random.randint(1000, 9999)
        """
        result = send_message(phone,random_code)
        if not result:
            return Response({"status": False, 'message': '短信发送失败'})
        """
        print(random_code)
        """
        conn = get_redis_connection()
        conn.set(phone, random_code, ex=60)
        """
        return Response({"status": True, 'message': '发送成功'})


class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        """"""

        # 正式操作
        """
        ser = LoginSerializer(data=request.data)
        if not ser.is_valid():
            return Response({"status": False, 'message': '验证码错误'})

        phone = ser.validated_data.get('phone')
        nickname = ser.validated_data.get('nickname')
        avatar = ser.validated_data.get('avatar')
        """
        # 临时操作
        phone = request.data.get('phone')
        nickname = request.data.get('nickname')
        avatar = request.data.get('avatar')

        user_object, flag = models.UserInfo.objects.get_or_create(
            telephone=phone,
            defaults={
                "nickname": nickname,
                'avatar': avatar}
        )
        user_object.token = str(uuid.uuid4())
        user_object.save()

        return Response({"status": True, "data": {"token": user_object.token, 'phone': phone}})
auth.py
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.generics import CreateAPIView, ListAPIView,RetrieveAPIView
from rest_framework import status
from api import models

from django.forms import model_to_dict
from django.db.models import F

from utils.auth import GeneralAuthentication,UserAuthentication
# APIView.dispatch
class NewsModelSerializer(serializers.ModelSerializer):
    user = serializers.SerializerMethodField()
    topic = serializers.SerializerMethodField()

    class Meta:
        model = models.News
        fields = ['id', 'cover', 'content', 'topic', "user", 'favor_count']

    def get_user(self, obj):
        return model_to_dict(obj.user, fields=['id', 'nickname', 'avatar'])

    def get_topic(self, obj):
        if not obj.topic:
            return
        return model_to_dict(obj.topic, fields=['id', 'title'])


# ############################# 动态列表 #############################
from utils.filters import MaxFilterBackend, MinFilterBackend
from utils.pagination import OldBoyLimitPagination

# 查看动态列表接口
# 发布接口
class NewsView(ListAPIView):
    serializer_class = NewsModelSerializer
    queryset = models.News.objects.all().order_by('-id')

    pagination_class = OldBoyLimitPagination
    filter_backends = [MinFilterBackend, MaxFilterBackend]


# ############################# 动态详细 #############################

class NewsDetailModelSerializer(serializers.ModelSerializer):
    images = serializers.SerializerMethodField()
    create_date = serializers.DateTimeField(format="%Y-%m-%d %H:%M")

    user = serializers.SerializerMethodField()
    topic = serializers.SerializerMethodField()

    viewer = serializers.SerializerMethodField()
    comment = serializers.SerializerMethodField()

    is_favor = serializers.SerializerMethodField()

    class Meta:
        model = models.News
        exclude = ['cover',]

    def get_images(self,obj):
        detail_queryset = models.NewsDetail.objects.filter(news=obj)
        # return [row.cos_path for row in detail_queryset]
        # return [{'id':row.id,'path':row.cos_path} for row in detail_queryset]
        return [model_to_dict(row,['id','cos_path']) for row in detail_queryset]

    def get_user(self, obj):
        return model_to_dict(obj.user, fields=['id', 'nickname', 'avatar'])

    def get_topic(self, obj):
        if not obj.topic:
            return
        return model_to_dict(obj.topic, fields=['id', 'title'])

    def get_viewer(self,obj):
        # 根据新闻的对象 obj(news)
        # viewer_queryset = models.ViewerRecord.objects.filter(news_id=obj.id).order_by('-id')[0:10]
        queryset = models.ViewerRecord.objects.filter(news_id=obj.id)
        viewer_object_list = queryset.order_by('-id')[0:10]
        context = {
            'count':queryset.count(),
            'result': [model_to_dict(row.user,['nickname','avatar']) for row in viewer_object_list]
        }
        return context

    def get_comment(self,obj):
        """
        获取所有的1级评论,再给每个1级评论获取一个耳机评论。
        :param obj:
        :return:
        """

        # 1.获取所有的 一级 评论
        first_queryset = models.CommentRecord.objects.filter(news=obj,depth=1).order_by('id')[0:10].values(
            'id',
            'content',
            'depth',
            'user__nickname',
            'user__avatar',
            'create_date'
        )
        first_id_list = [ item['id'] for item in first_queryset]
        # 2.获取所有的二级评论
        # second_queryset = models.CommentRecord.objects.filter(news=obj,depth=2)
        # 2. 获取所有1级评论下的二级评论
        # second_queryset = models.CommentRecord.objects.filter(news=obj, depth=2,reply_id__in=first_id_list)
        # 2. 获取所有1级评论下的二级评论(每个二级评论只取最新的一条)
        from django.db.models import Max
        result = models.CommentRecord.objects.filter(news=obj, depth=2, reply_id__in=first_id_list).values('reply_id').annotate(max_id=Max('id'))
        second_id_list = [item['max_id'] for item in result] # 5, 8

        second_queryset = models.CommentRecord.objects.filter(id__in=second_id_list).values(
            'id',
            'content',
            'depth',
            'user__nickname',
            'user__avatar',
            'create_date',
            'reply_id',
            'reply__user__nickname'
        )

        import collections
        first_dict = collections.OrderedDict()
        for item in first_queryset:
            item['create_date'] = item['create_date'].strftime('%Y-%m-%d')
            first_dict[item['id']] = item

        for node in second_queryset:
            first_dict[node['reply_id']]['child'] = [node,]

        return first_dict.values()

    def get_is_favor(self,obj):
        # 1. 用户未登录
        user_object = self.context['request'].user
        if not user_object:
            return False

        # 2. 用户已登录
        exists = models.NewsFavorRecord.objects.filter(user=user_object,news=obj).exists()
        return exists


class NewsDetailView(RetrieveAPIView):
    queryset = models.News.objects
    serializer_class = NewsDetailModelSerializer

    def get(self,request, *args,**kwargs):

        response = super().get(request, *args,**kwargs)
        if not request.user:
            return response
        # 判断当前用户是否有访问此新闻的记录?
        news_object = self.get_object() # models.News.objects.get(pk=pk)
        exists = models.ViewerRecord.objects.filter(user=request.user,news=news_object).exists()
        if exists:
            return response
        models.ViewerRecord.objects.create(user=request.user,news=news_object)
        models.News.objects.filter(id=news_object.id).update(viewer_count=F('viewer_count')+1)

        return response

# ############################# 话题  #############################
class TopicModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Topic
        fields = "__all__"


class TopicView(ListAPIView):
    serializer_class = TopicModelSerializer
    queryset = models.Topic.objects.all().order_by('-id')

    pagination_class = OldBoyLimitPagination
    filter_backends = [MinFilterBackend, MaxFilterBackend]


# ############################# 获取所有子评论()  #############################
"""
 {
    "id": 5,
    "content": "1-2",
    "user__nickname": "大卫-6",
    "user__avatar": "https://mini-1251317460.cos.ap-chengdu.myqcloud.com/08a9daei1578736867828.png",
    "create_date": "2020-01-15T07:46:35.434290Z",
    "reply_id": 1,
    "reply__user__nickname": "wupeiqi"
}

"""

class CommentModelSerializer(serializers.ModelSerializer):
    create_date = serializers.DateTimeField(format='%Y-%m-%d')
    user__nickname = serializers.CharField(source='user.nickname')
    user__avatar = serializers.CharField(source='user.avatar')
    reply_id = serializers.CharField(source='reply.id')
    reply__user__nickname = serializers.CharField(source='reply.user.nickname')
    class Meta:
        model = models.CommentRecord
        exclude = ['news','user','reply','root']


class CreateCommentModelSerializer(serializers.ModelSerializer):
    create_date = serializers.DateTimeField(format='%Y-%m-%d',read_only=True)
    user__nickname = serializers.CharField(source='user.nickname',read_only=True)
    user__avatar = serializers.CharField(source='user.avatar',read_only=True)
    reply_id = serializers.CharField(source='reply.id',read_only=True)
    reply__user__nickname = serializers.CharField(source='reply.user.nickname',read_only=True)

    class Meta:
        model = models.CommentRecord
        # fields = "__all__"
        exclude = ['user','favor_count']


class CommentView(APIView):

    def get_authenticators(self):
        if self.request.method == 'POST':
            return [UserAuthentication(), ]
        return [GeneralAuthentication(), ]

    def get(self,request,*args,**kwargs):
        root_id = request.query_params.get('root')
        # 1. 获取这个根评论的所有子孙评论
        node_queryset = models.CommentRecord.objects.filter(root_id=root_id).order_by('id')
        # 2. 序列化
        ser = CommentModelSerializer(instance=node_queryset,many=True)

        return Response(ser.data,status=status.HTTP_200_OK)

    def post(self,request,*args,**kwargs):
        # 1. 进行数据校验: news/depth/reply/content/root
        ser = CreateCommentModelSerializer(data=request.data)
        if ser.is_valid():
            # 保存到数据库
            ser.save(user_id=1)
            # 对新增到的数据值进行序列化(数据格式需要调整)
            news_id = ser.data.get('news')
            models.News.objects.filter(id=news_id).update(comment_count=F('comment_count')+1)

            return Response(ser.data,status=status.HTTP_201_CREATED)
        return Response(ser.errors,status=status.HTTP_400_BAD_REQUEST)


# ############################ 新闻点赞 ##########################

class FavorModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.NewsFavorRecord
        fields = ['news']


class FavorView(APIView):
    authentication_classes = [UserAuthentication,]
    def post(self,request,*args,**kwargs):
        ser = FavorModelSerializer(data=request.data)
        if not ser.is_valid():
            return Response({},status=status.HTTP_400_BAD_REQUEST)
        news_object = ser.validated_data.get('news')
        queryset = models.NewsFavorRecord.objects.filter(user=request.user,news=news_object)
        exists = queryset.exists()
        if exists:
            queryset.delete()
            return Response({},status=status.HTTP_200_OK)
        models.NewsFavorRecord.objects.create(user=request.user,news=news_object)
        return Response({},status=status.HTTP_201_CREATED)



class TestSER(serializers.ModelSerializer):
    title = serializers.SerializerMethodField()
    class Meta:
        model = models.Topic
        fields = "__all__"

    def get_title(self,obj):
        request = self.context['request']

class TestView(ListAPIView):
    queryset = models.Topic.objects
    serializer_class = TestSER



class TestView(ListAPIView,CreateAPIView):
    queryset = models.Topic.objects
    serializer_class = TestSER
news.py
作者:华王 博客:https://www.cnblogs.com/huahuawang/
原文地址:https://www.cnblogs.com/huahuawang/p/14815286.html