Django-4

看起來可能零散,但重在查漏補缺。python

建立 utils 的 python 文件,專門用於存放咱們本身寫的公用的功能。redis

建立 forms 的 python 文件,專門用於校驗。sql

已知 views 用於視圖邏輯的實現、models 用於定義各類表結構數據庫

1、公共方法抽成公用的類

上次結尾講到的對錶的增刪改查(四種請求方式),以及模糊查詢、過濾、分頁,這樣的功能比較常見,因此抽成單獨的類,使其具備普適性。django

 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 是必定要給的,不然不知道要查詢哪張表/未加上校驗,其餘的能夠根據具體需求來決定。json

  • search_field = [] —— 支持模糊查詢的字段
  • filter_field = [] —— 支持過濾的字段
  • model = None —— 要查詢哪張表
  • form_class = None —— 根據哪一個 Form 來校驗

舉例在 views 中對以上分類的使用(繼續使用 cvb 的方式,由於能夠用類繼承的方法,很方便,這是函數沒有的)app

class StudentView(Nbview):
    search_field = ['name','phone','addr','work_addr'] #存放搜索的字段
    filter_field = ['name','phone','id','addr','work_addr'] #指定哪些字段能夠過濾
    model = Student
    form_class = forms.StudentForm

2、修改 Django 自帶的用戶操做系統

給 user 增長 phone,如何修改自帶的表結構,要在 setting 中修改設置(AUTH_USER_MODEL = 'user.MyUser' #指定Django使用本身指定的user表)框架

由於有外鍵聯繫,須要一開始建表的時候就建好,這裏把 sqllite3 刪除,migrateion 中除了 __init__ 其餘全刪除函數

實現用郵箱、用戶名、手機號均可以登陸的功能post

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)

3、中間件

全部請求開始以前都走中間件,再到 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 中

4、多表操做

實際使用中的場景多是這樣的:  在文章表中,如今寫的 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 表產生關聯。

相關文章
相關標籤/搜索