首页 > 其他 > 详细

Django-4

时间:2019-11-06 09:43:07      阅读:83      评论:0      收藏:0      [点我收藏+]

看起来可能零散,但重在查漏补缺。

创建 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 是一定要给的,否则不知道要查询哪张表/未加上校验,其他的可以根据具体需求来决定。

  • search_field = [] —— 支持模糊查询的字段
  • filter_field = [] —— 支持过滤的字段
  • model = None —— 要查询哪张表
  • form_class = None —— 根据哪个 Form 来校验

举例在 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

二、修改 Django 自带的用户操作系统

给 user 增加 phone,如何修改自带的表结构,要在 setting 中修改设置(AUTH_USER_MODEL = ‘user.MyUser‘ #指定Django使用自己指定的user表)

因为有外键联系,需要一开始建表的时候就建好,这里把 sqllite3 删除,migrateion 中除了 __init__ 其他全删除

实现用邮箱、用户名、手机号都可以登录的功能

1. 建表

重新创建表的过程中可以发现,有一些字段是每个表中都带的,这时考虑整一个 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

其他表需要用这几个字段时只需要继承这个类。

2. 登录用户的账号校验

下面来实现用户名、邮箱和手机号都能登录的功能(在对登录的校验中实现)

 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,这样后面可以直接用这个用户的其他信息。

3. token 的使用

 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 来验证?

4. pickle

不管什么类型的数据都可以使用 pickle 转换为字符串。注意是bytes类型的。以下为 pickle 的使用举例,包含转换和解析。

import pickle

    class M:
    name=beijing

    c=M()
    result = pickle.dumps(c)    #result为bytes类型

    #解析
    name = pickle.loads(s)

三、中间件

所有请求开始之前都走中间件,再到 views。利用这一方法,可以在到某个 views 之前,强行让使用者登录。当然这样会遇到问题,比如:打开登录界面依然提示使用者先登录,所以在登录页面,我们需要将其排除登录的这个限制。

1. PutMethodMiddleware

在说登录功能之前,之前有一个问题是 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。

2. SessionMiddleware

实现在到 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 表产生关联。

Django-4

原文:https://www.cnblogs.com/april-aaa/p/11790252.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!