看起来可能零散,但重在查漏补缺。
创建 utils 的 python 文件,专门用于存放我们自己写的公用的功能。
创建 forms 的 python 文件,专门用于校验。
已知 views 用于视图逻辑的实现、models 用于定义各种表结构
上次结尾讲到的对表的增删改查(四种请求方式),以及模糊查询、过滤、分页,这样的功能比较常见,所以抽成单独的类,使其具有普适性。
1 class Nbview(View): 2 search_field = [] 3 filter_field = [] 4 model = None 5 form_class = None 6 7 def get_filter_dict(self): 8 filter_dict = {} # 这个是用来过滤数据的字典 {‘id‘:1,‘name‘:‘abc‘,‘phone‘:xxx} id=1,name=abc,phone=xxx 9 for field in self.filter_field: # 循环获取到有哪些过滤的字段 10 value = self.request.GET.get(field) 11 if value: 12 filter_dict[field] = value 13 return filter_dict 14 15 def get_search_obj(self): 16 q_result = Q() # 保存模糊查询的对象 17 # 模糊查询是在给出的几个字段中找出指定内容,filter是在特定字段筛选 18 search = self.request.GET.get(‘search‘) # python 19 if search: 20 for field in self.search_field: 21 d = {‘%s__contains‘%field:search} #{name__contains:abc} 22 q_result = Q(**d)|q_result 23 return q_result 24 25 26 27 def get_paginator_obj(self,data): 28 try: 29 limit = int(self.request.GET.get(‘limit‘,const.page_limit)) 30 page = int(self.request.GET.get(‘page‘,1)) 31 except: 32 limit = const.page_limit 33 page = 1 34 page_obj = Paginator(data, limit) 35 stus = list(page_obj.get_page(page)) 36 return stus,page_obj 37 38 39 def get(self,request): 40 filter_dict = self.get_filter_dict() 41 q_result = self.get_search_obj() 42 query_set = self.model.objects.filter(**filter_dict).filter(q_result).values() 43 data,page_obj = self.get_paginator_obj(query_set) 44 return NbResponse(data=data,count=page_obj.count) 45 46 def post(self,request): 47 form_obj = self.form_class(request.POST) 48 if form_obj.is_valid(): 49 self.model.objects.create(**form_obj.cleaned_data) 50 ret = NbResponse() 51 else: 52 ret = NbResponse(error_code=-1, msg=form_obj.error_format) 53 return ret 54 55 def delete(self,request): 56 id = request.GET.get(‘id‘,0) 57 self.model.objects.filter(id=id).delete() 58 return NbResponse() 59 60 def put(self,request):#修改 61 put_data,files = request.parse_file_upload(request.META,request) 62 # put_data,files = PutMethodMiddleware() 63 form_obj = self.form_class.StudentForm(put_data) #它校验的是全部的字段 64 id = request.GET.get(‘id‘,0) 65 clean_data = {} 66 if form_obj.is_valid(): 67 clean_data = form_obj.cleaned_data 68 else: 69 error_keys = set(form_obj.errors.get_json_data().keys()) #校验没有通过的字段 70 put_keys = set(put_data.keys())#这个传过来的字段 71 if error_keys & put_keys:#如果有交集,说明传过来的字段不对 72 return NbResponse(error_code=-1,msg=form_obj.error_format) 73 else:#没有交集说明穿过的字段都通过了校验 74 for k in put_keys: 75 clean_data[k] = put_data.get(k) # 76 self.model.objects.filter(id=id).update(**clean_data) 77 return NbResponse()
在实际使用的过程中,只需要声明以下几个变量,其中 model 和 form_class 是一定要给的,否则不知道要查询哪张表/未加上校验,其他的可以根据具体需求来决定。
举例在 views 中对以上分类的使用(继续使用 cvb 的方式,因为可以用类继承的方法,很方便,这是函数没有的)
class StudentView(Nbview): search_field = [‘name‘,‘phone‘,‘addr‘,‘work_addr‘] #存放搜索的字段 filter_field = [‘name‘,‘phone‘,‘id‘,‘addr‘,‘work_addr‘] #指定哪些字段可以过滤 model = Student form_class = forms.StudentForm
给 user 增加 phone,如何修改自带的表结构,要在 setting 中修改设置(AUTH_USER_MODEL = ‘user.MyUser‘ #指定Django使用自己指定的user表)
因为有外键联系,需要一开始建表的时候就建好,这里把 sqllite3 删除,migrateion 中除了 __init__ 其他全删除
实现用邮箱、用户名、手机号都可以登录的功能
重新创建表的过程中可以发现,有一些字段是每个表中都带的,这时考虑整一个 BaseModel,用于创建那些公共字段,比如创建时间、修改时间。
class BaseModel(models.Model): ‘‘‘公共字段‘‘‘ is_delete_choice = ( #枚举,定义时要使用二维元组 (0,‘删除‘), (1,‘正常‘) ) is_delete = models.SmallIntegerField(choices=is_delete_choice,default=1,verbose_name=‘是否被删除‘) create_time = models.DateTimeField(verbose_name=‘创建时间‘,auto_now_add=True)#auto_now_add的意思,插入数据的时候,自动取当前时间 update_time = models.DateTimeField(verbose_name=‘修改时间‘,auto_now=True)#修改数据的时候,时间会自动变 class Meta: abstract = True #只是用来继承的
因为这个 Model 只是用来继承的,不需要真实单独创建出来,所以声明:abstract = True
其他表需要用这几个字段时只需要继承这个类。
下面来实现用户名、邮箱和手机号都能登录的功能(在对登录的校验中实现)
1 class LoginForm(Form,FormErrorFormat): 2 username = forms.CharField(min_length=6,max_length=20,required=True) 3 password = forms.CharField(min_length=8,max_length=20,required=True) 4 5 def clean(self): 6 username = self.cleaned_data[‘username‘] 7 password = self.cleaned_data[‘password‘] 8 user = models.MyUser.objects.filter(Q(username=username) | Q(phone=username) | Q(email=username)) 9 if user: 10 user = user.first() 11 if user.check_password(password): 12 self.cleaned_data[‘user‘] = user #这里的user是user的所有信息 13 else: 14 raise ValidationError(‘密码错误‘) 15 else: 16 raise ValidationError(‘用户不存在‘)
self.cleand_data 存放的是通过校验的字段,这里 self.cleand_data[‘user‘] = user, 是把数据库拿到的 QuerySet 放到了 cleand_data,这样后面可以直接用这个用户的其他信息。
1 class LoginView(View): 2 3 def get(self,request): 4 user_form_obj = LoginForm(request.GET) 5 if user_form_obj.is_valid(): 6 user = user_form_obj.cleaned_data[‘user‘] 7 # user_form_obj是一个对象,从LoginForm实例化出来的 8 token = ‘%s%s‘%(user.username,time.time()) 9 token = md5(token) 10 redis = django_redis.get_redis_connection() #使用默认 11 # redis = django_redis.get_redis_connection(‘redis2‘) 12 redis.set(token,pickle.dumps(user),const.token_expire) #user是对象,不能直接塞到redis 13 14 return NbResponse(token=token) 15 else: 16 return NbResponse(-1,user_form_obj.error_format)
这里把用户数据库存储的用户名+登录时的时间戳拼接到一起当做 token,存到 Redis 时 user 和 token 分别作为 key 和 value,从常量文件中设置一个 token 过期时间。
这里因为 user 是从数据库查到的信息,相当于是 MyUser 的实例化对象(?不也是从数据库查到的QuerySet?是一回事吗),不能直接存到 Redis,使用了 pickle 这个模块。
但是还不清楚 token 要用在哪,或许是用户其他操作需要 token 来验证?
不管什么类型的数据都可以使用 pickle 转换为字符串。注意是bytes类型的。以下为 pickle 的使用举例,包含转换和解析。
import pickle class M: name=‘beijing‘ c=M() result = pickle.dumps(c) #result为bytes类型 #解析 name = pickle.loads(s)
所有请求开始之前都走中间件,再到 views。利用这一方法,可以在到某个 views 之前,强行让使用者登录。当然这样会遇到问题,比如:打开登录界面依然提示使用者先登录,所以在登录页面,我们需要将其排除登录的这个限制。
在说登录功能之前,之前有一个问题是 request.PUT 并没有这样的方法,我们使用的是 request.META, 继续使用后者当然也没问题,不过为了看上去整齐划一,可以在中间件中将 request.PUT 封装一下,之后就可以直接用了,具体操作如下:
1 class PutMethodMiddleware(MiddlewareMixin): 2 3 def process_request(self,request): #所有请求之前先走到这,再去请求views 4 # print(‘request.path_info‘,request.path_info) #请求的地址 5 # print(‘request.path‘,request.path) 6 # print(‘request.get_full_path‘,request.get_full_path()) 7 # print(‘token‘,request.headers.get(‘token‘)) 8 # print(‘request.FILES‘,request.FILES) 9 # print("中间件接收到请求") 10 # return JsonResponse({"ok":1}) 11 if request.method == ‘PUT‘: 12 put_data,files = request.parse_file_upload(request.META, request) 13 request.PUT = put_data #request是实例化对象? 14 request._files = files #是因为request.files取值的时候就是_files 15 16 17 def process_response(self,request,response): #views响应之后走到这 18 # print("中间件接收到响应") 19 return response 20 21 # 处理完views走到这 22 def process_view(self, request, view_func, view_args, view_kwargs): 23 pass 24 25 # print("中间件在执行%s视图前" %view_func.__name__) 26 27 # views报错走到这 28 def process_exception(self,request,exception): 29 # print("中间件处理视图异常...") 30 pass
封装之后 request.PUT 的使用修改为:
1 def put(self,request):#修改 2 # put_data,files = request.parse_file_upload(request.META,request) 3 form_obj = self.form_class(request.PUT) #它校验的是全部的字段 4 id = request.GET.get(‘id‘,0) 5 clean_data = {} 6 if form_obj.is_valid(): 7 clean_data = form_obj.cleaned_data 8 else: 9 error_keys = set(form_obj.errors.get_json_data().keys()) #校验没有通过的字段 10 put_keys = set(request.PUT.keys())#这个传过来的字段 11 if error_keys & put_keys:#如果有交集,说明传过来的字段不对 12 return NbResponse(error_code=-1,msg=form_obj.error_format) 13 else:#没有交集说明穿过的字段都通过了校验 14 for k in put_keys: 15 clean_data[k] = request.PUT.get(k) # 16 self.model.objects.filter(id=id).update(**clean_data) 17 return NbResponse()
put_data 用 request.PUT 代替,如果要用到 file,直接用 request.flies。
实现在到 views 之前先登录的功能。(no_login_list 是存在常量文件中的 list)
1 class SessionMiddleware(MiddlewareMixin): 2 3 def process_request(self,request): #所有请求之前先走到这,再去请求views 4 # print(‘request.path_info‘,request.path_info)#请求的地址 5 for uri in no_login_list: 6 if uri in request.path_info: 7 break 8 else: 9 # token = request.META.get(‘http_token‘.upper()) #从header获取数据 10 token = request.GET.get(‘token‘) 11 redis = django_redis.get_redis_connection() 12 13 if token: 14 user_bytes = redis.get(token) 15 if user_bytes: 16 user = pickle.loads(user_bytes)#从redis获取到用户信息 17 request.user = user #加到request 18 request.token = token #request里面添加token 19 else: 20 return NbResponse(-1,‘请登录‘) 21 else: 22 return NbResponse(-1,‘请登录!‘)
补充:request 在这里是一个类的实例化,django 的框架中的特性。
这里使用了 token!
使用 token 来验证用户登录:
请求中如果未拿到 token,说明用户未登录,需要登录。(因为登录之后会产生token)
如果请求中有 token,1. 验证 Redis 中是否能取到 token,说明 token 未过期,以上 user 是 username+token 生成时的时间戳,token 是对这里 user 的 md5 加密。
2. Redis 中未取到 token,说明 token 已过期
?1. 实际应该加个校验吧?看请求中传的 token 与 Redis 中的是否对的上。
?2. 从Redis取token的时候和用户对应上了吗
对以上两个疑问的回答:
token 是请求带来的(username+时间戳)
user_bytes 是从 Redis 中取出来的 value(上面的 token 是 key,所以对应的上),对其进行解析得到存在 Redis 中的 user(用户在数据库的整条信息)
最后把 user 和 token 信息放到 request 中
实际使用中的场景可能是这样的: 在文章表中,现在写的 NbView 只能满足于它自己表的一些操作,但我还想把导航(Nav)的具体内容显示出来,可是文章表中只有 nav_id 这个字段。这时候需要重写方法。
1 class ArticleView(Nbview): 2 search_field = [‘title‘,‘content‘] 3 filter_field = [‘id‘,‘title‘] 4 model = Article 5 form_class = forms.ArticleForm 6 7 def get(self,request): 8 filter_dict = self.get_filter_dict() 9 q_result = self.get_search_obj() 10 query_set = self.model.objects.filter(**filter_dict).filter(q_result) #加.values会变成字典 11 ret = [] 12 for obj in query_set: 13 d = { 14 ‘id‘:obj.id, 15 ‘title‘:obj.title, 16 ‘content‘:obj.img, 17 ‘nav_id‘:obj.nav_id, 18 ‘nav_name‘:obj.nav.name, 19 ‘create_time‘:obj.create_time, 20 ‘update_time‘:obj.update_time, 21 } 22 ret.append(d) 23 data,page_obj = self.get_paginator_obj(ret) 24 return NbResponse(data = data,count = page_obj.count)
上面用到导航名的时候直接 obj.nav.name,这是因为本身文章表就有 nav_id 这个外键与 Nav 表产生关联。
原文:https://www.cnblogs.com/april-aaa/p/11790252.html