第十五篇 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 常用状态码列表
- 错误处理,状态码是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)
img_file = request.FILES['avatar'] #图片文件等
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. 序列化
# serializer中获取request def create(self, validated_data): request = self.context["request"] # 1.创建新闻资讯 new_object = models.News.objects.create(recommend_count=1, **validated_data) # 2.推荐记录 models.Recommend.objects.create( news=new_object, user=request.user ) return new_object
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}}
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
from django.contrib import admin from . import models # Register your models here. for table in models.__all__: admin.site.register(getattr(models, table))
rom django.urls import path, include from .views import BookView, BookEditView urlpatterns = [ path('list', BookView.as_view()), path('retrieve/<int:id>', BookEditView.as_view()), ]
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("")
# 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}}
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()), ]
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("不存在的版本")
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" }
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
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()
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()), ]
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("认证测试")
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)
REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": ["utils.auth.MyAuth", ] }
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()), ]
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("认证测试")
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
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ "web.utils.TestAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "web.utils.TestPermission", ], }
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("认证测试")
# 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
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" } }
10. 分页
from django.urls import path from .views import BookView urlpatterns = [ path('book', BookView.as_view()), ]
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)
# 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"
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解析器的测试~~")
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()), ]
#!/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)
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
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")
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测试")
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
""" 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", } }
from django.contrib import admin # Register your models here. from . import models for table in models.__all__: admin.site.register(getattr(models, table))
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')
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}) ]
utils
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.error = None @property def dict(self): return self.__dict__
# 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
# by gaoxin import redis POOL = redis.ConnectionPool(host="127.0.0.1", port=6379, decode_responses=True, max_connections=10)
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()), ]
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
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))
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()), ]
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)
# 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"]
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()), ]
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)
小程序后端
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')), ]
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()), ]
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')
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
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('手机格式错误')
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}})
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
作者:华王
博客:https://www.cnblogs.com/huahuawang/