django的一些用法的積累與總結

前言

最近又要用到django去搭建網站,之前用的東西需要花一些時間去恢復記憶,同時還需要學習一些新的知識,進一步提升自己使用django的能力,因此寫這篇博客持續更新使用過程中積累的一些技巧,知識和用法等。
目前我自己寫了一個認證系統,包括登入登出,註冊激活,密碼找回等功能,放在了github倉庫中,有使用django-registration-redux不適合的,或者需要個性化定製認證功能的,可以考慮使用和優化這個認證系統。以下的代碼大部分來自於該項目。

發送郵件設置

要使用django發送郵件,事先需要做一些設置,這裏我以騰訊企業郵箱爲例。

  • 打開郵箱的smtp發送郵件服務,一般在設置中的客戶端設置中
    在這裏插入圖片描述
  • 然後在yourapp/settings中設置如下內容
EMAIL_HOST = 'smtp.exmail.qq.com'
EMAIL_PORT = '465'
EMAIL_HOST_USER = '[email protected]'
EMAIL_HOST_PASSWORD = 'xxxx'
DEFAULT_FROM_EMAIL = '[email protected]'
EMAIL_USE_SSL = True
  • 之後使用django.core.mail中的send_mail發送郵件即可
from django.core.mail import send_mail
send_mail(subject,message,from_email,recipient_list)
#subject:發送主題
#message:發送內容
#from_email:對應settings中的DEFAULT_FROM_EMAIL
#recipient_list 發送人列表,例如[[email protected], [email protected]]

保存用戶密碼

實際上,無論是django.forms.Form,還是django.forms.ModelForm,都僅僅是發揮表單提交和表單驗證的功能,用戶的密碼在存到數據庫的時候是不會加密的,因此,需要手動加密,一般的方法,這裏引用django基礎教程中的方法,先保存form模型到數據庫,然後再根據用戶名提取到user對象,之後再使用user.set_password()的方法給密碼加密,例子如下:

if request.method == 'POST':
        form = Registration_Form(request.POST)
        if form.is_valid():
            userform = form.save(commit=False)
            userform.is_active = False #初始用戶未激活
            userform.save()

            username = form.cleaned_data['username']
            email = form.cleaned_data['email']
            user = User.objects.get(username=username)
            user.set_password(user.password) #form保存時不會自動加密,因此這裏需要手動加密
            user.userauth = UserAuth(user=user)
            user.save()
            user.userauth.send_activation_email(request,purpose='registration')
            return render(request,'user_auth/registration_complete.html',{'username':username,
            'email':email})
        else:
            print(form.errors)
            return render(request,'user_auth/registration.html',{'form':form})
            
    form = Registration_Form()
    return render(request,'user_auth/registration.html',{'form':form}) 

自定義form表單驗證

要對錶單的一些字段或者內容進行自定義驗證的話,可以通過在form 類中添加clean()和clean_yourfield()方法來增加驗證內容,如下的例子,自定義方法驗證郵箱的唯一性和兩次輸入的密碼是否相同的form類,需要注意的是django默認的User中郵箱是可以重複的,因此我們需要自己去檢查郵箱的唯一性。另外,除去User模型中有的字段,我們可以額外添加一些字段或者重寫其中的字段,而Meta類中的field是有順序的,如果你在模板中使用for循環來遍歷字段的話,它將決定最終字段的顯示的順序。

class Registration_Form(forms.ModelForm):

    username = forms.CharField(max_length=64,label='用戶名',
        widget=forms.TextInput(attrs={'placeholder':'用戶名'}),
        # error_messages={'required':'不能爲空','max_length':'要求64個字符以內'}
        )
    confirm_password = forms.CharField(label='重新輸入密碼',max_length=64,widget=forms.PasswordInput(attrs={'class':'form-control',
    'placeholder':'重複輸入密碼',
    }))
    password = forms.CharField(label='登陸密碼',max_length=64,widget=forms.PasswordInput(attrs={'class':'form-control',
    'placeholder':'登陸密碼',
    }))
    email = forms.EmailField(label='用戶郵箱',widget=forms.EmailInput(attrs={'placeholder':'用戶郵箱'}))
    
    class Meta:
        model = User
        fields = ['username','password','confirm_password','email'] #添加confirm_password以調整順序

    def clean(self):
        if self.cleaned_data['password'] != self.cleaned_data['confirm_password']:
            raise forms.ValidationError('兩次密碼輸入不一致,請檢查!',code='password error')
           
        return self.cleaned_data

    def clean_email(self): #函數名是嚴格限制的 clean+下劃線+字段
        """
        用於驗證郵箱的唯一性
        """
        if User.objects.filter(email__exact=self.cleaned_data['email']):
            raise forms.ValidationError('該Email已經註冊過了,請檢查!',code='invalid email')
        return self.cleaned_data['email']

可以看到,這裏還使用了forms.ValidationError來自定義錯誤說明,它們最終將被添加到form.errors中。
這裏特別說明的是在ModelForm中的字段,最終是先驗證ModelForm中的字段,然後再驗證自定義的字段,因此如果我這裏想寫一個clean_password(self)的方法來使用self.cleaned_data[‘confirm_password’]就會KeyError的的錯誤,因此最好的額辦法是寫一個clean_confirm_password(self)的方法中引用self.cleaned_data[‘password’],因爲此時的password的key已經存在了!

模板中有多個參數的url寫法

{% url 'user_auth:registration_resend_email' username email %}

這裏的username和email都是即將傳遞給名爲registration_resend_email的URL的參數,最終找到的URL如下:

'registration/resend_email/<username>/<email>/'

激活碼的生成

通過查看django-registration-redux的源碼,學習到了如何將用戶激活的功能集成到User的關聯模型中,我們只需要創建一個與User 有OneToOneField()的Userauth模型,然後在該模型下寫認證功能,可以直接引用self.user和self.save()以及self.yourfield來構建激活碼的生成及驗證,以下是代碼:

from datetime import datetime,timedelta
import hashlib
import string
import os

from django.db import models
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.utils.crypto import get_random_string
from django.conf import settings 

class UserAuth(models.Model):
    
    user = models.OneToOneField(User,on_delete=models.CASCADE)

    activation_time = models.DateTimeField(default=datetime.now())

    activation_key = models.CharField(max_length=64,blank=True)

    activation_valid = models.BooleanField(default=False)

    def __str__(self):
        return self.user

    def generate_activation_key(self,save=True):
        """
        生成64位隨機激活碼
        """
        random_string = get_random_string(length=32,allowed_chars=string.printable)
        self.activation_key = hashlib.sha256(random_string.encode('utf-8')).hexdigest()

        if save:
            self.save()

        return self.activation_key

    def send_activation_email(self,request,purpose='reset_password'):
        """
        用於給用戶發送註冊確認郵件或者密碼重置激活郵件
        purpose = [ reset_password,registration ]
        這裏特別需要注意的是activation_url的構建
        purpose的設置是以urls.py中設置的路徑關聯的
        因爲在urls.py中設置了激活URL爲:
        resent_password/activation/(P<activation_key>[\w-]+)
        registration/activation/(P<activation_key>[\w-]+)
        因此,這裏我拼了一個這樣的激活路徑,如果後續修改了url,那麼
        這裏的activation_url也必須要修改以適應新的激活URL
        """
        activation_key = self.generate_activation_key(save=True)
        activation_url = 'http://' + "/".join([request.META['HTTP_HOST'],
                'accounts',purpose,'activation',activation_key])
        if purpose == 'registration':
            subject = getattr(settings,'REGISTRATION_SUBJECT')
            message = getattr(settings,'REGISTRATION_MESSAGE')
        elif purpose == 'reset_password':
            subject = getattr(settings,'RESET_PASSWORD_SUBJECT')
            message = getattr(settings,'RESET_PASSWORD_MESSAGE')
        if subject and message:
            message = message.format(
                username=self.user.username,
                activation_url=activation_url,
                sender=getattr(settings,'EMAIL_HOST_USER'),
                time=datetime.now())

        from_email = getattr(settings,'DEFAULT_FROM_EMAIL')
        recipient_list = [ self.user.email ]
        send_mail(subject,message,from_email,recipient_list)
        #保存發送激活碼時間
        self.activation_time = datetime.now()
        self.activation_valid = True
        self.save()
        return True
    
    def confirm_activation_key(self):
        """
        確認激活碼是否有效且驗證時間未過期
        這裏特別需要說明的是activation_valid值
        它主要是用來保證用戶激活及密碼重置以後
        原來的激活或者密碼重置界面將無法使用
        默認情況下是false
        """
        expired_days = getattr(settings,'EXPIRED_DAYS') or 1
        is_not_expired = self.activation_time + timedelta(expired_days) > datetime.now()
        is_valid = self.activation_valid
        return is_valid and is_not_expired
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章