pt35项目登录装饰器-request
4、地址管理功能API
新建地址模型类
#user/models.pyclass Address(models.Model):"""收货地址表"""# 外键: 和UserProfile 一对多user_profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE)# 收件人 地址(没做拆分) 邮编 手机号receiver = models.CharField(verbose_name="收件人", max_length=10)address = models.CharField(verbose_name="收件地址", max_length=100)postcode = models.CharField(verbose_name="邮编", max_length=6)receiver_mobile = models.CharField(verbose_name="手机号", max_length=11)# 标签tag:家 公司 宿舍tag = models.CharField(verbose_name="标签", max_length=10)# 默认地址is_default = models.BooleanField(verbose_name="是否为默认地址", default=False)# 伪删除is_active = models.BooleanField(verbose_name="是否活跃", default=True)# 创建时间 和 更新时间created_time = models.DateTimeField(auto_now_add=True)updated_time = models.DateTimeField(auto_now=True)class Meta:db_table = "user_address"
> python manage.py makemigrations
> python manage.py migrate # 数据库验证下
初步设计路由&函数
接口文档路由 URL:http://localhost:8000/v1/users/<username>/address
#user/urls.py# 地址管理[新增和查询]:v1/users/<>/addresspath('<str:username>/address', views.AddressView.as_view()),#views.不能直接是类, 使用class自带的as_view()方法
#user/views.pyfrom django.views import View #注意import 这个 大写的 Viewclass AddressView(View):"""使用CBV:类视图(函数试图 FBV)"""def get(self, request, username):return JsonResponse({'code': 200})def post(self, request, username):passdef put(self, request):passdef delete(self, request):pass
FBV和CBV
FBV:Function Base View 函数视图
CBV:Class Base View 类视图CBV应用场景- 一个路由提供了多种HTTP请求方式的支持时,使用类视图- 类视图更适合resetful语义,一个地址对应多种操作[GET POST PUT DELETE]的场景CBV优点- 代码可读性好- 类视图对于函数视图有更高的复用性,如果项目中其他模块需要用到某个类视图的某个特定逻辑时,直接继承该类即可
CBV使用流程
views.py中定义视图类
from django.views import View #注意import这个 大写的 Viewclass XxxView(View):def get(self, request):passdef post(self, request):pass
urls.py中添加路由
from . import viewspath('xxx/xxx', views.XxxView.as_view()),# path中的第2个参数必须是视图函数,不能是类,所以需要使用as_view() 方法处理
校验token问题
前端传递token
// addressAdmin.html 前端 获取本地的token加到请求头里,给后端$.ajax({url:baseUrl+'/v1/users/'+username+'/address',type:'get',beforeSend:function(request){// 获取本地的token加到请求头里request.setRequestHeader("authorization",localStorage.getItem('dashop_token'))},success(data){// console.log(data);if(data.code == 403){alert('用户认证已过期,请重新登录');
后端获取token
# django 自动处理的请求头
#前端header里authorization被处理成大写并在前面追加HTTP_,变成 HTTP_AUTHORIZATION
#后端获取如下:request.META.get('HTTP_AUTHORIZATION')
考虑到每一步操作都需要校验token ,使用装饰器,新建名为utils的pythonpackage的工具包
设计登录检查装饰器
设计装饰器
新建名为utils的目录作为pythonpackage工具包
#utils/logging_dec.py 与 dashopt user 同级目录import jwtfrom django.conf import settings
from django.http import JsonResponse
from user.models import UserProfiledef logging_check(func):def wrapper(self, request, *args, **kwargs):# token: request.META.get('HTTP_AUTHORIZATION')# 1.获取tokentoken = request.META.get('HTTP_AUTHORIZATION')if not token:return JsonResponse({"code": 403, "error": "Please login"})#前端addressAdmin.html 接收到403,会弹出重新登录,# 2.校验tokentry:payload = jwt.decode(token, settings.JWT_TOKEN_KEY, algorithms="HS256")except Exception as e:print('jwt decode error is %s' % e)return JsonResponse({"code": 403, "error": "Please login"})# 装饰器添加myuser属性,方便给所有使用该装饰器的方法调用# 因为我们只信任token中的usernameusername = payload["username"]user = UserProfile.objects.get(username=username) #import UserProfilerequest.myuser = user return func(self, request, *args, **kwargs)return wrapper
前端接收
// addressAdmin.html$.ajax({url:baseUrl+'/v1/users/'+username+'/address',type:'get',beforeSend:function(request){request.setRequestHeader("authorization",localStorage.getItem('dashop_token'))},success(data){// console.log(data);if(data.code == 403){alert('用户认证已过期,请重新登录');window.localStorage.removeItem('dashop_user');window.localStorage.removeItem('dashop_token');window.localStorage.removeItem('dashop_count');location.href = 'login.html'}
后端使用装饰器
#user/views.py
...
from django.views import View
from utils.logging_dec import logging_checkclass AddressView(View):"""CBV:类视图"""@logging_checkdef get(self, request, username):logging_user = request.myuserreturn JsonResponse({"code":200, "username":logging_user.username})
验证
http://localhost:7000/dadashop/templates/index.html 登录,查看我的地址
查看xhr请求里的 address get请求的Response {"code": 200, "username": "user01"}
新增地址
#user/views.py
...
from .models import Addressfrom utils.logging_dec import logging_check
from django.views import Viewclass AddressView(View):"""CBV:类视图"""@logging_checkdef get(self, request, username):"""查询收货地址视图逻辑{"code":200, "addresslist":[{},...]}"""logging_user = request.myuserreturn JsonResponse({"code":200, "username":logging_user.username})@logging_checkdef post(self, request, username):"""新增收货地址视图逻辑1.获取请求体数据 接口文档2.存入数据表[Address]2.1 第一个地址,新增并设置默认地址2.2 非第一个地址,正常新增地址非默认地址3.组织数据返回"""data = json.loads(request.body)receiver = data.get('receiver')receiver_phone = data.get('receiver_phone')address = data.get('address')postcode = data.get('postcode')tag = data.get('tag')# 查询该用户之前是否有收货地址user = request.myuserold_address = Address.objects.filter(user_profile=user, is_active=True)is_default = Falseif not old_address:# 之前无地址,此地址设置为默认地址is_default = True# 存数据表Address.objects.create(user_profile=user,receiver=receiver,address=address,receiver_mobile=receiver_phone,postcode=postcode,tag=tag,is_default=is_default)result = {"code": 200,"data": "新增地址成功!"}return JsonResponse(result)
新增地址,去数据库里查询验证
查询地址
#user/views.py
...
class AddressView(View):@logging_checkdef get(self, request, username):"""查询收货地址视图逻辑{"code":200, "addresslist":[{},...]}"""user = request.myuser# all_address: QuerySetall_address = Address.objects.filter(user_profile=user, is_active=True)# 组织数据返回 接口文档addresslist = []for address in all_address:add_dict = {}add_dict['id'] = address.idadd_dict['address'] = address.addressadd_dict['receiver'] = address.receiveradd_dict['receiver_mobile'] = address.receiver_mobileadd_dict['tag'] = address.tagadd_dict['postcode'] = address.postcodeadd_dict['is_default'] = address.is_default# 添加到地址列表中addresslist.append(add_dict)result = {"code": 200,"addresslist": addresslist}return JsonResponse(result)
删除、修改地址
URL:http://127.0.0.1:8000/v1/users/<username>/address/<id>
#user/urls.py# 地址管理[修改和删除]:v1/users/<username>/address/<id>path('<str:username>/address/<int:id>', views.AddressView.as_view()),
#user/views.py
...
class AddressView(View):@logging_checkdef delete(self, request, username, id):"""删除收货地址视图逻辑1.默认地址不能删除2.获取请求体数据[地址的id]3.判断是否为默认地址3.1 是默认地址,不能删,直接返回3.2 非默认地址,删除,is_active=False4.组织数据返回"""# 1.查询地址try:address = Address.objects.get(id=id)except Exception as e:result = {"code": 10105,"error": "get address error"}return JsonResponse(result)# 2.判断是否为默认地址if address.is_default:# 默认地址不能删除result = {"code": 10106,"error": "default address is not allowed delete"}return JsonResponse(result)# 3.删除[伪删除is_active=False]try:address.is_active = Falseaddress.save()except Exception as e:result = {"code": 10107,"error": "delete address error"}return JsonResponse(result)# 4.正确响应返回result = {"code": 200,"data": "删除地址成功"}return JsonResponse(result)@logging_checkdef put(self, request, username, id):"""更新收货地址视图逻辑1.获取请求体数据2.一查二改三保存3.组织数据返回"""data = json.loads(request.body)receiver = data.get("receiver")receiver_phone = data.get("receiver_mobile")address = data.get("address")tag = data.get("tag")try:add = Address.objects.get(id=id)except Exception as e:result = {'code': 10108, 'error': 'get address error'}return JsonResponse(result)add.receiver = receiveradd.receiver_mobile = receiver_phoneadd.address = addressadd.tag = tagadd.save()return JsonResponse({"code": 200, "data": "修改地址成功"})
设为默认地址
设置新的默认地址,取消旧的默认地址,涉及事务。
from django.db import transactionwith transaction.atomic():# 1.创建存储点sid = transaction.savepoint()# 2.事务中各个操作# 3.事务提交transaction.savepoint_commit(sid)# 4.事务回滚transaction.savepoint_rollback(sid)
URL:http://127.0.0.1:8000/v1/users/<username>/address/default
#user/urls.py# 默认地址:v1/users/zhaoliying/address/default path('<str:username>/address/default', views.DefaultAddressView.as_view()),
#user/views.py
from django.db import transaction
...
class DefaultAddressView(View):@logging_checkdef post(self, request, username):"""设置默认地址视图逻辑1.获取请求体数据[{"id":xxx}]2.查询该地址3.修改保存[当前设置默认 取消原来默认]-事务4.组织数据返回"""data = json.loads(request.body)address_id = data.get("id")# 开启事务with transaction.atomic():# 1.创建存储点sid = transaction.savepoint()try:# 设置原来默认地址为非默认old_default = Address.objects.get(user_profile=request.myuser, is_default=True, is_active=True)old_default.is_default = Falseold_default.save()# 设置地址为默认地址new_default = Address.objects.get(id=address_id, is_active=True)new_default.is_default = Truenew_default.save()except Exception as e:# 回滚transaction.savepoint_rollback(sid)return JsonResponse({"code": 10109, "error": "set default address error"})# 提交事务transaction.savepoint_commit(sid)return JsonResponse({"code": 200, "data": "设为默认成功"})
抽象模型类
模型基类,可以为普通模型类补充字段,减少代码重复
创建
#utils/basemodel.pyfrom django.db import modelsclass BaseModel(models.Model):"""模型基类,为普通模型类补充字段抽象模型类补充创建时间和更新时间两个字段"""# 创建时间 和 更新时间created_time = models.DateTimeField(auto_now_add=True)updated_time = models.DateTimeField(auto_now=True)class Meta:# 声明此模型类为抽象模型类abstract = True
使用
#user/models.py
...
from utils.basemodel import BaseModel
...class UserProfile(BaseModel):
#class UserProfile(models.Model): 先前继承的系统,现在继承自建的# 创建时间 和 更新时间 去掉不用了#created_time = models.DateTimeField(auto_now_add=True)#updated_time = models.DateTimeField(auto_now=True)class Address(BaseModel):
# class Address(models.Model):
#user/migrations 删除 0001... 0002...开头的迁移文件drop database dashopt;
create database dashopt default charset utf8;> python manage.py makemigrations
> python manage.py migrate # 数据库验证下
requests模块
Python中非常优秀的第三方请求库,支持多种HTTP METHOD请求,官方 urllib功能类似,不好用
在开发中经常用来做单元测试,也是爬虫中使用频率最高的请求库 sudo pip3 install requests###常用方法- requests.get()- requests.post()- requests.put()- requests.delete()###方法中的参数- url :请求的URL地址,字符串- json :请求体的数据,字典{}- headers :请求头#####
resp = requests.post(url=url, json=data, headers=headers)- resp.json() :获取响应内容[Python对象]- resp.text : 获取响应内容 - 字符串- resp.status_code : 获取HTTP响应码### 1.5 测试dadashop登录功能[POST]
# http://127.0.0.1:8000/v1/tokens"""测试dadashop登录模块:POST请求
"""
import requestsurl = "http://127.0.0.1:8000/v1/tokens"
data = {"username": "user01","password": "123456","carts": "0"
}#resp = requests.post(url=url, json=data)
#print(resp) #<Response [200]>resp = requests.post(url=url, json=data).json()
print(resp)# 错误的 :{'code': 10200, 'error': 'The username is wrong'}
{'code': 200, 'username': 'user01', 'data': {'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIwMSIsImV4cCI6MTY4NTYzNDMxMC42MjIzNTI2fQ.YRzSazEazRQ0sxyIJv4QW7znjYxHABkgmm0If8CCwlg'}, 'carts_count': 0}"""测试查询地址功能-GET请求
"""
import requestsurl = "http://127.0.0.1:8000/v1/users/user01/address"
headers = {"authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIwMSIsImV4cCI6MTY4NTYzNDMxMC42MjIzNTI2fQ.YRzSazEazRQ0sxyIJv4QW7znjYxHABkgmm0If8CCwlg",
}resp = requests.get(url=url, headers=headers).json()print(resp){'code': 200, 'addresslist': [{'id': 1, 'address': '北京市北京市市辖区东城区幸福大街', 'receiver': 'user01', 'receiver_mobile': '17723452345', 'tag': '幸福', 'postcode': '511000', 'is_default': True}]}
"""修改地址功能测试 - put请求http://127.0.0.1:8000/v1/users/<username>/address/<id>
"""import requestsurl = "http://127.0.0.1:8000/v1/users/zhaoliying/address/3"
data = {'receiver': 'user01','receiver_mobile': '13723452345','address': '幸福大街','tag': '公司',
}
headers = {"authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVzZXIwMSIsImV4cCI6MTY4NTYzNDMxMC42MjIzNTI2fQ.YRzSazEazRQ0sxyIJv4QW7znjYxHABkgmm0If8CCwlg",
}resp = requests.put(url=url, json=data, headers=headers).json()print(resp)
短信注册
http://localhost:7000/dadashop/templates/register_sms.html
CBV中dispatch()方法
django类视图[CBV],所有过来的请求都会经过dispatch()方法,根据请求方法的不同执行对应的视图
class DefaultAddressView(View): 关于View,源码定义了操作方法class View:http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']def dispatch(self, request, *args, **kwargs): #去做method的分发
可以通过重写dispatch()方法,实现登录状态的校验,实现对类内所有视图方法增强|修饰功能
from django.views import Viewclass XxxView(View):def dispatch(self, request, *args, **kwargs):# 1.通用逻辑 | 装饰逻辑 | 修饰逻辑# 2.不能破坏原有的分发逻辑return super().dispatch(request, *args, **kwargs)def get(self, request):pass