练习
多方式登陆接口
需求:多方式登陆接口:用户名+密码、邮箱+密码、手机号+密码
serializer.py
from rest_framework import serializers
from .models import UserInfo
from rest_framework_jwt.views import ObtainJSONWebToken
import re
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
from rest_framework.exceptions import ValidationError
class LoginSerializer(serializers.ModelSerializer):
class Meta:
model=UserInfo
fields = ['username','password'] # username 是unique唯一的,反序列化校验的时候,字段自己的规则,会去数据库查询有没有这个用户,如果有,直接报错了
username = serializers.CharField() # 这个优先用这个,就不是映射过来的,就没有unique的限制了
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if re.match('^1(3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8[0-9]|9[0-9])\d{8}$',username):
user= UserInfo.objects.filter(phone=username).first()
print(username)
elif re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)",username):
user = UserInfo.objects.filter(email=username).first()
else:
user =UserInfo.objects.filter(username=username).first()
#根据密码查找
if user.check_password(password) and user:
# 签发token
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.user = user
self.token = token
return attrs
else:
raise ValidationError('用户名或密码错误')
views.py
from rest_framework.viewsets import ViewSet
from .serializer import LoginSerializer
from rest_framework.response import Response
class LoginView(ViewSet):
def login(self,request):
ser =LoginSerializer(data=request.data)
if ser.is_valid():
token = ser.token
return Response({'code':100,'msg':'ok','token':token})
else:
return Response({'code': 100, 'msg':ser.errors})
urls.py
from django.contrib import admin
from django.urls import path
from app01.views import LoginView
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',LoginView.as_view({'post':'login'})),
]
总结
1.在author扩展表中,username设置的字段参数是唯一的:unique=True,所以在序列化类的时候,如果使用ModelSerializer映射字段的时候,要重写username:username = serializers.CharField()
2.在反序列化全局钩子校验的时候(def validate(self, attrs)),其中attrs是一个字典,并且是经过字段自己校验和局部校验后的数据字典,且反序列全局钩子的返回值必须是一个字典attrs,
3.在视图里面,想要得到反序列化得到值:可以在序列化类中使用:self.属性=属性值,在视图类中在校验之后的数据中:ser.属性得到属性值
4.在视图里面,想要得到全局钩子的返回值,校验通过之后使用ser.validated_data得到
5.在视图函数里面,数据校验通过之后,也可以手动调用视图里面的函数
自定义频率类
需求:写出频率类,每个ip,一分钟只能访问3次
自定义的逻辑
1.取出访问者ip:request.META.get('REMOTE_ADDR')
2.判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问,在字典里,继续往下走
3.循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
4.判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
5.当大于等于3,说明一分钟内访问超过三次,返回False验证失败
class MyThrottles():
VISIT_RECORD = {}
def __init__(self):
self.history=None
def allow_request(self,request, view):
#(1)取出访问者ip
# print(request.META)
ip=request.META.get('REMOTE_ADDR')
import time
ctime=time.time()
# (2)判断当前ip不在访问字典里,添加进去,并且直接返回True,表示第一次访问
if ip not in self.VISIT_RECORD:
self.VISIT_RECORD[ip]=[ctime,]
return True
self.history=self.VISIT_RECORD.get(ip)
# (3)循环判断当前ip的列表,有值,并且当前时间减去列表的最后一个时间大于60s,把这种数据pop掉,这样列表中只有60s以内的访问时间,
while self.history and ctime-self.history[-1]>60:
self.history.pop()
# (4)判断,当列表小于3,说明一分钟以内访问不足三次,把当前时间插入到列表第一个位置,返回True,顺利通过
# (5)当大于等于3,说明一分钟内访问超过三次,返回False验证失败
if len(self.history)<3:
self.history.insert(0,ctime)
return True
else:
return False
def wait(self):
import time
ctime=time.time()
return 60-(ctime-self.history[-1])
需求:写出频率类,每个ip,一分钟只能访问5次
throttl.py
from rest_framework.throttling import SimpleRateThrottle
import time
class common_throttling:
dic_ip = {}
def __init__(self):
self.time_list=None
def allow_request(self, request, view):
ip = request.META.get('REMOTE_ADDR')
# 如果在字典中没有ip,就可以把这个ip,时间添加进去,并返回true
ctime = time.time()
if ip not in self.dic_ip:
self.dic_ip[ip] = [ctime]
return True
# 如果ip在字典里的话,取出列表
self.time_list= self.dic_ip[ip]
# 判断如果当前时间减去列表中最后一个值的时间,如果大雨60就弹出
if ctime-self.time_list[-1]>60:
self.time_list.pop()
# 如果此时现在列表长度大于=3,就返回false
if len(self.time_list) >=5:
return False
else:
self.time_list.insert(0,ctime)
self.dic_ip[ip]=self.time_list
return True
def wait(self):
import time
ctime = time.time()
return 60 - (ctime - self.time_list[-1])
views.py
from .throttl import common_throttling
class BookView(ViewSet):
throttle_classes = [common_throttling]
def get(self,request):
return Response({'ok'})
urls.py
from django.contrib import admin
from django.urls import path
from app01.views import BookView
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', BookView.as_view({'get':'get'})),
]
基于APIView写分页
views.py
from rest_framework.views import APIView
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin
from .page import common_page
from .models import Book
from .serializer import BookSerializer
class BookView(APIView):
def get(self,request):
# page = self.paginate_queryset(queryset)
obj_list = Book.objects.all()
pagenation = common_page()
page = pagenation.paginate_queryset(obj_list, request, self)
qs = BookSerializer(page,many=True)
print(qs)
# return pagenation.get_paginated_response(qs.data)
return Response({'code':100,
'mag':'查询成功',
'count':pagenation.page.paginator.count,
'next': pagenation.get_next_link(),
'previous':pagenation.get_previous_link(),
'results':qs.data})
page.py
from rest_framework.pagination import PageNumberPagination
class common_page(PageNumberPagination):
page_size = 2
page_query_param = 'page'
page_size_query_param = "page_size"
max_page_size = 5
全局异常处理
exception.py
from rest_framework.views import exception_handler
from rest_framework.response import Response
def common_exception_handler(exc, context):
# APIView对drf做了异常处理,没有对其他报错做处理
res = exception_handler(exc, context)
if res:
# drf
if isinstance(res.data,dict):
detail = res.data.get('detail')
else:
detail = res.data
return Response({'code': 102, 'msg': detail})
else:
print(type(exc)) # <class 'IndexError'>
detail = str(exc)
# return Response({'code': 999, 'msg': detail})
return Response({'code': 999, 'msg': '系统异常,请联系系统管理员'})
settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'app01.exception.common_exception_handler',
}
auth扩展表补充
1.一开始没有使用auth的user表,做过迁移了
1.后期再想扩展,其实就不行了
2.如果真做了这个事,还要行
1.把所有的迁移文件删除(自己的app,源码中的(admin,auth))的migrations
2.删库
3.重新迁移
auth 模块回顾
1.Auth模块:是Django自带的用户认证模块
1.我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这是个麻烦的事情。
2.Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,它默认使用 auth_user 表来存储用户数据。
2.超级用户创建:pthon manage.py createsuperuser
用户登陆
1.保存用户信息
方式一:
request.session['username'] = 获取到用户对象.username
方式二:
from django.contrib import auth
auth.login(request,用户对象)
ps:如果你用了auth,就用一整套。只要你用了auth.login方法,后续就可以在任何方法里面通过:request.user拿到用户对象。eg:request.user.username --->user_obj.username
用户认证
1.判断用户登陆成功
方式一:
request.session.get('username')
方式二:
request.user.is_authenticated() --->true false
2.认证装饰器
1.1 导模块:from django.contrib.auth.decorators import login_required
1.2 装饰器:@login_required(),默认提交:/account/login/
1.局部添加:@login_required(login_url ='/xxx/')-->未登陆跳转/xxx/
2.全局添加:@login_required()-->在配置文件中加:LOGIN_URL = '/login/',未登陆跳转
修改密码
1.验证老密码:request.user.check_password(旧密码)
2.修改密码:
1.request.user.set_password(新密码)-->这一句没有真正的操作数据库(update)
2.request.user.save()-->真正的操作数据库(update)
3.request.user-->返回的是用户对象;如果返回的是AnonymousUser,就是匿名用户,未登陆
注销登录
1.注销用户:
1.导模块:from django.contrib import auth
2.注销用户:auth.logout(request)
2.当调用该函数时,当前请求的session信息会全部清除
用户注册
1.导模块:from django.contrib.auth.models import User
2.注册用户名密码
创建不加密:creat()-->User.objects.create(username=username,password=password)
创建加密的:creat_user()-->User.objects.create_user(username=username,password=password)
创建超级用户:create_superuser() --> User.objects.create_superuser(username=username,email='123@qq.com',password=password # 邮箱必须填
局部钩子全局钩子源码
局部钩子
全局钩子
many参数作用
1.如果传了many=True,序列化多条---->得到的对象不是BookSerializer对象,而是ListSerialzier的对象中套了一个个的BookSerializer对象
2.如果传了many=False,序列化单条---》得到的对象就是BookSerializer的对象
3.类实例化得到对象,是谁控制的? -->__new__()
# BaseSerializer有__new__()方法
def __new__(cls, *args, **kwargs):
# We override this method in order to automatically create
# `ListSerializer` classes instead when `many=True` is set.
if kwargs.pop('many', False):
return cls.many_init(*args, **kwargs)
return super().__new__(cls, *args, **kwargs)
many_init---->BaseSerializer中的类
@classmethod
def many_init(cls, *args, **kwargs):
...
meta = getattr(cls, 'Meta', None)
list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer)
return list_serializer_class(*args, **list_kwargs)
视图集回顾
1.ViewSetMixin:只要继承他,路由写法变了
2.ViewSet:ViewSetMixin, views.APIView
3.GenericViewSet:(ViewSetMixin, generics.GenericAPIView)
4.ModelViewSet:
1、继承了ModelViewSet,没增加一条记录,就干某个事,重写perform_create
2、序列化使用一个序列化类,反序列化使用配置的那个序列化类
3、自定义了一个方法:login,使用了action装饰器,让它取到的qs对象和序列化类跟配置的都不一样
5.ReadOnlyModelViewSet(mixins.RetrieveModelMixin,mixins.ListModelMixin,GenericViewSet)
视图类 :self 内部有request对象,还有个action,就是视图类中方法的字符串名
练习
继承了ModelViewSet,每增加一条记录,就干某个事,重写perform_create
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 每次添加数据的时候,打印hello
def perform_create(self, serializer):
print('hello')
serializer.save()
序列化使用一个序列化类,反序列化使用配置的那个序列化类
from .models import Book
from .serializer import BookSerializer,PublishSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 序列化使用一个序列化类,反序列化使用配置的那个序列化类
def get_serializer_class(self):
if self.request.method == 'GET':
return PublishSerializer
else:
return self.serializer_class
自定义了一个方法:login,使用了action装饰器,让它取到的qs对象和序列化类跟配置的都不一样
from .models import Book,Publish
from .serializer import BookSerializer,PublishSerializer
from rest_framework.viewsets import ModelViewSet
from rest_framework.mixins import ListModelMixin
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 序列化使用一个序列化类,反序列化使用配置的那个序列化类
def get_queryset(self):
if 'login' in self.request.path:
return "单独的qs"
else:
return super().get_queryset()
def get_serializer_class(self):
if "login" in self.request.path:
return '某个序列化类'
return self.serializer_class
@action(methods=['POST'])
def login(self,request):
self.get_queryset()
self.get_serializer()
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 序列化使用一个序列化类,反序列化使用配置的那个序列化类
def get_queryset(self):
if self.action == 'login':
return "单独的qs"
else:
return super().get_queryset()
def get_serializer_class(self):
if self.action == 'login':
return '某个序列化类'
return self.serializer_class
@action(methods=['POST'])
def login(self,request):
self.get_queryset()
self.get_serializer()
自定义中间件
1.django默认提供的有七个中间件,它还支持程序员自己定义中间件
2.自定中间件的步骤
1.在项目名下或者应用名下新建一个任意名称的文件夹
2.在这个文件夹下面新建一个py文件
3.在这个py文件中,新建一个类,必须继承MiddlewareMixin
4.在这个新建的类下面写两个方法:
1.process_reqeust
2.process_response
5.一定要在配置文件的中间件里面注册你的中间件路径
3.需求:创建自定义的中间件
Settings.py
MIDDLEWARE = [
'app01.mymiddleware.mymiddleware1'
]
app01/mymiddleware/mymiddleware1
from django.utils.deprecation import MiddlewareMixin
import time
from django.shortcuts import HttpResponse
class mymiddleware1(MiddlewareMixin):
def __init__(self, get_response):
self.time_list = None
self.get_response = get_response
self.dic_ip = {}
def process_request(self,request):
ip = request.META.get('REMOTE_ADDR')
# 如果在字典中没有ip,就可以把这个ip,时间添加进去,并返回true
ctime = time.time()
if ip not in self.dic_ip:
self.dic_ip[ip] = [ctime]
return
# 如果ip在字典里的话,取出列表
self.time_list = self.dic_ip[ip]
# 判断如果当前时间减去列表中最后一个值的时间,如果大雨60就弹出
if ctime - self.time_list[-1] > 60:
self.time_list.pop()
# 如果此时现在列表长度大于=3,就返回false
if len(self.time_list) >= 5:
return HttpResponse('限制了被', status=400)
else:
self.time_list.insert(0, ctime)
self.dic_ip[ip] = self.time_list
return
def process_response(self, request, response):
print('我是第一个中间件的process_response')
return response # 本质返回的也是HttpResponse
补充
1.魔法方法:类中使用__开头 __结尾的方法,它不需要主动调用,某种情况下会自动触发object类身上的