最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Django工程的分層結(jié)構(gòu)詳解

 更新時(shí)間:2019年07月18日 16:59:02   投稿:laozhang  
在本篇文章里小編給各位分享的是關(guān)于Django工程的分層結(jié)構(gòu)的相關(guān)知識(shí)點(diǎn)內(nèi)容,有興趣的朋友們學(xué)習(xí)參考下。

前言

傳統(tǒng)上我們都知道在Django中的MTV模式,具體內(nèi)容含義我們?cè)賮砘仡櫼幌拢?/p>

M:是Model的簡稱,它的目標(biāo)就是通過定義模型來處理和數(shù)據(jù)庫進(jìn)行交互,有了這一層或者這種類型的對(duì)象,我們就可以通過對(duì)象來操作數(shù)據(jù)。

V:是View的簡稱,它的工作很少,就是接受用戶請(qǐng)求換句話說就是通過HTTP請(qǐng)求接受用戶的輸入;另外把輸入信息發(fā)送給處理程并獲取結(jié)果;最后把結(jié)果發(fā)送給用戶,當(dāng)然最后這一步還可以使用模板來修飾數(shù)據(jù)。

T:是Template的簡稱,這里主要是通過標(biāo)記語言來定義頁面,另外還可以嵌入模板語言讓引擎來渲染動(dòng)態(tài)數(shù)據(jù)。

這時(shí)候我們看到網(wǎng)上大多數(shù)的列子包括有些視頻課程里面只講MVT以及語法和其他功能實(shí)現(xiàn)等,但大家有沒有想過一個(gè)問題,你的業(yè)務(wù)邏輯放在哪里?課程中的邏輯通常放在了View里面,就像下面:

# urls.py
path('hello/', Hello),
path('helloworld/', HelloWorld.as_view())

# View
from django.views import View

# FVB
def Hello(request):
 if request.method == "GET":
 return HttpResponse("Hello world")

# CVB
class HelloWorld(View):
 def get(self, request):
 pass
 def post(self, request):
 pass

無論是FBV還是CBV,當(dāng)用戶請(qǐng)求進(jìn)來并通過URL路由找到對(duì)應(yīng)的方法或者類,然后對(duì)請(qǐng)求進(jìn)行處理,比如可以直接返回模型數(shù)據(jù)、驗(yàn)證用戶輸入或者校驗(yàn)用戶名和密碼等。在學(xué)習(xí)階段或者功能非常簡單的時(shí)候使用這種寫法沒問題,但是對(duì)于相對(duì)大一點(diǎn)的項(xiàng)目來說你很多具體的處理流程開始出現(xiàn),而這些東西都寫到View里顯然你自己都看不下去。

FBV全名Function-based views,基于函數(shù)的視圖;CBV全名Class-based views,基于類的視圖

所以View,它就是一個(gè)控制器,它不應(yīng)該包含業(yè)務(wù)邏輯,事實(shí)上它應(yīng)該是一個(gè)很薄的層。

業(yè)務(wù)邏輯到底放哪里

網(wǎng)上也有很多文章回答了這個(gè)問題,提到了Form層,這個(gè)其實(shí)是用于驗(yàn)證用戶輸入數(shù)據(jù)的格式,比如郵件地址是否正確、是否填寫了用戶名和密碼,至于這個(gè)用戶名或者郵箱到底在數(shù)據(jù)庫中是否真實(shí)存在則不是它應(yīng)該關(guān)心的,它只是一個(gè)數(shù)據(jù)格式驗(yàn)證器。所以業(yè)務(wù)邏輯到底放哪里呢?顯然要引入另外一層。

關(guān)于這一層的名稱有些人叫做UseCase,也有些人叫做Service,至于什么名字無所謂只要是大家一看就明白的名稱就好。如果我們使用UseCase這個(gè)名字,那么我們的Djaong工程架構(gòu)就變成了MUVT,如果是Service那么就MSVT。

這一層的目標(biāo)是什么呢?它專注于具體業(yè)務(wù)邏輯,也就是不同用例的具體操作,比如用戶注冊(cè)、登陸和注銷都一個(gè)用例。所有模型都只是工作流程的一部分并且這一層也知道模型有哪些API。這么說有些空洞,我們用一個(gè)例子來說明:

場(chǎng)景是用戶注冊(cè):

  • 信息填寫規(guī)范且用戶不存在則注冊(cè)成功并發(fā)送賬戶激活郵件
  • 如果用戶已存在則程序引發(fā)錯(cuò)誤,然后傳遞到上層并進(jìn)行告知用戶名已被占用

Django 2.2.1、Python 3.7

下圖是整個(gè)工程的結(jié)構(gòu)

Models層

models.py

from django.db import models
from django.utils.translation import gettext as _

# Create your models here.

from django.contrib.auth.models import AbstractUser, UserManager, User

class UserAccountManager(UserManager):
 # 管理器
 def find_by_username(self, username):
 queryset = self.get_queryset()
 return queryset.filter(username=username)


class UserAccount(AbstractUser):
 # 擴(kuò)展一個(gè)字段,家庭住址
 home_address = models.CharField(_('home address'), max_length=150, blank=True)
 # 賬戶是否被激活,與users表里默認(rèn)的is_active不是一回事
 is_activated = models.BooleanField(_('activatition'), default=False, help_text=_('新賬戶注冊(cè)后是否通過郵件驗(yàn)證激活。'),)

 # 指定該模型的manager類
 objects = UserAccountManager()

我們知道Django會(huì)為我們自動(dòng)建立一個(gè)叫做auth_user的表,也就是它自己的認(rèn)證內(nèi)容,這個(gè)user表本身就是一個(gè)模型,它就是繼承了AbstractUser類,而這個(gè)類有繼承了AbstractBaseUser,而這個(gè)類繼承了models.Model,所以我們這里就是一個(gè)模型。再說回AbstractUser類,這個(gè)類里面定義了一些username、first_name、email、is_active等用戶屬性相關(guān)的字段,如果你覺得不夠用還可以自己擴(kuò)展。

為了讓Django使用我們擴(kuò)展的用戶模型,所以需要在settings.py中添加如下內(nèi)容:

AUTH_USER_MODEL = "users.UserAccount"

工具類

這個(gè)文件主要是放一些通用工具,比如發(fā)送郵件這種公共會(huì)調(diào)用的功能,utils.py內(nèi)容如下:

from django.core.mail import send_mail
from django.contrib.sites.shortcuts import get_current_site
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
from django.template.loader import render_to_string
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_text
from mysite import settings


class TokenGenerator(PasswordResetTokenGenerator):
 def __init__(self):
 super(TokenGenerator, self).__init__()

 # def _make_hash_value(self, user, timestamp):
 # return (
 #  six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active)
 # )


class WelcomeEmail:
 subject = 'Activate Your Account'

 @classmethod
 def send_to(cls, request, user_account):
 try:
  current_site = get_current_site(request)
  account_activation_token = TokenGenerator()
  message = render_to_string('activate_account.html', {
  'username': user_account.username,
  'domain': current_site.domain,
  'uid': urlsafe_base64_encode(force_bytes(user_account.id)),
  'token': account_activation_token.make_token(user_account),
  })

  send_mail(
  subject=cls.subject,
  message=message,
  from_email=settings.EMAIL_HOST_USER,
  recipient_list=[user_account.email]
  )
 except Exception as err:
  print(err)

TokenGenerator這個(gè)東西使用還是它父類本身的功能,之所以這樣做是為了在必要的時(shí)候可以重寫一些功能。父類PasswordResetTokenGenerator的功能主要是根據(jù)用戶主鍵來生成token,之后還會(huì)根據(jù)傳遞的token和用戶主鍵去檢查傳遞的token是否一致。

針對(duì)郵件發(fā)送我這里使用Django提供的封裝,你需要在settings.py中添加如下內(nèi)容:

# 郵件設(shè)置
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_SSL = True
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 465
EMAIL_HOST_USER = '' # 發(fā)件人郵箱地址
EMAIL_HOST_PASSWORD = '' # 發(fā)件人郵箱密碼
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

Services層

這層主要是根據(jù)用例來實(shí)現(xiàn)業(yè)務(wù)邏輯,比如注冊(cè)用戶賬號(hào)和激活用戶賬號(hào)。

"""
Service層,針對(duì)不同用例實(shí)現(xiàn)的業(yè)務(wù)邏輯代碼
"""
from django.utils.translation import gettext as _
from django.shortcuts import render
from .utils import (
 WelcomeEmail,
 TokenGenerator,
)
from users.models import (
 UserAccount
)


class UsernameAlreadyExistError(Exception):
 pass


class UserIdIsNotExistError(Exception):
 """
 用戶ID,主鍵不存在
 """
 pass


class ActivatitionTokenError(Exception):
 pass


class RegisterUserAccount:
 def __init__(self, request, username, password, confirm_password, email):
 self._username = username
 self._password = password
 self._email = email
 self._request = request

 def valid_data(self):
 """
 檢查用戶名是否已經(jīng)被注冊(cè)
 :return:
 """
 user_query_set = UserAccount.objects.find_by_username(username=self._username).first()
 if user_query_set:
  error_msg = ('用戶名 {} 已被注冊(cè),請(qǐng)更換。'.format(self._username))
  raise UsernameAlreadyExistError(_(error_msg))
 return True

 def _send_welcome_email_to(self, user_account):
 """
 注冊(cè)成功后發(fā)送電子郵件
 :param user_account:
 :return:
 """
 WelcomeEmail.send_to(self._request, user_account)

 def execute(self):
 self.valid_data()
 user_account = self._factory_user_account()
 self._send_welcome_email_to(user_account)
 return user_account

 def _factory_user_account(self):
 """
 這里是創(chuàng)建用戶
 :return:
 """

 # 這樣創(chuàng)建需要調(diào)用save()
 # ua = UserAccount(username=self._username, password=self._password, email=self._email)
 # ua.save()
 # return ua

 # 直接通過create_user則不需要調(diào)用save()
 return UserAccount.objects.create_user(
  self._username,
  self._email,
  self._password,
 )


class ActivateUserAccount:
 def __init__(self, uid, token):
 self._uid = uid
 self._token = token

 def _account_valid(self):
 """
 驗(yàn)證用戶是否存在
 :return: 模型對(duì)象或者None
 """
 return UserAccount.objects.all().get(id=self._uid)

 def execute(self):
 # 查詢是否有用戶
 user_account = self._account_valid()
 account_activation_token = TokenGenerator()
 if user_account is None:
  error_msg = ('激活用戶失敗,提供的用戶標(biāo)識(shí) {} 不正確,無此用戶。'.format(self._uid))
  raise UserIdIsNotExistError(_(error_msg))

 if not account_activation_token.check_token(user_account, self._token):
  error_msg = ('激活用戶失敗,提供的Token {} 不正確。'.format(self._token))
  raise ActivatitionTokenError(_(error_msg))

 user_account.is_activated = True
 user_account.save()
 return True

這里定義的異常類比如UsernameAlreadyExistError等里面的內(nèi)容就是空的,目的是raise異常到自定義的異常中,這樣調(diào)用方通過try就可以捕獲,有些時(shí)候代碼執(zhí)行的結(jié)果影響調(diào)用方后續(xù)的處理,通常大家可能認(rèn)為需要通過返回值來判斷,比如True或者False,但通常這不是一個(gè)好辦法或者說在有些時(shí)候不是,因?yàn)槟菢訒?huì)造成代碼冗長,比如下面的代碼:

這是上面代碼中的一部分,

def valid_data(self):
 """
 檢查用戶名是否已經(jīng)被注冊(cè)
 :return:
 """
 user_query_set = UserAccount.objects.find_by_username(username=self._username).first()
 if user_query_set:
  error_msg = ('用戶名 {} 已被注冊(cè),請(qǐng)更換。'.format(self._username))
  raise UsernameAlreadyExistError(_(error_msg))
 return True

 def execute(self):
 self.valid_data()
 user_account = self._factory_user_account()
 self._send_welcome_email_to(user_account)
 return user_account

execute函數(shù)會(huì)執(zhí)行valid_data()函數(shù),如果執(zhí)行成功我才會(huì)向下執(zhí)行,可是你看我在execute函數(shù)中并沒有這樣的語句,比如:

def execute(self):
 if self.valid_data():
 user_account = self._factory_user_account()
 self._send_welcome_email_to(user_account)
 return user_account
 else:
 pass

換句話說你的每個(gè)函數(shù)都可能有返回值,如果每一個(gè)你都這樣寫代碼就太啰嗦了。其實(shí)你可以看到在valid_data函數(shù)中我的確返回了True,但是我希望你也應(yīng)該注意,如果用戶存在的話我并沒有返回False,而是raise一個(gè)異常,這樣這個(gè)異常就會(huì)被調(diào)用方獲取而且還能獲取錯(cuò)誤信息,這種方式將是一個(gè)很好的處理方式,具體你可以通過views.py中看到。

Forms表單驗(yàn)證

這里是對(duì)于用戶輸入做檢查

"""
表單驗(yàn)證功能
"""
from django import forms
from django.utils.translation import gettext as _


class RegisterAccountForm(forms.Form):
 username = forms.CharField(max_length=50, required=True, error_messages={
 'max_length': '用戶名不能超過50個(gè)字符',
 'required': '用戶名不能為空',
 })
 
 email = forms.EmailField(required=True)
 password = forms.CharField(min_length=6, max_length=20, required=True, widget=forms.PasswordInput())
 confirm_password = forms.CharField(min_length=6, max_length=20, required=True, widget=forms.PasswordInput())

 def clean_confirm_password(self) -> str: # -> str 表示的含義是函數(shù)返回值類型是str,在打印函數(shù)annotation的時(shí)候回顯示。
 """
 clean_XXXX XXXX是字段名
 比如這個(gè)方法是判斷兩次密碼是否一致,密碼框輸入的密碼就算符合規(guī)則但是也不代表兩個(gè)密碼一致,所以需要自己來進(jìn)行檢測(cè)
 :return:
 """
 password = self.cleaned_data.get('password')
 confirm_password = self.cleaned_data.get('confirm_password')

 if confirm_password != password:
  raise forms.ValidationError(message='Password and confirmation do not match each other')

 return confirm_password

前端可以實(shí)現(xiàn)輸入驗(yàn)證,但是也很容易被跳過,所以后端肯定也需要進(jìn)行操作,當(dāng)然我這里并沒有做預(yù)防XSS攻擊的措施,因?yàn)檫@個(gè)不是我們今天要討論的主要內(nèi)容。

Views

from django.shortcuts import render, HttpResponse, HttpResponseRedirect
from rest_framework.views import APIView
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.utils.encoding import force_bytes, force_text
from .forms import (
 RegisterAccountForm,
)
from .services import (
 RegisterUserAccount,
 UsernameAlreadyExistError,
 ActivateUserAccount,
 ActivatitionTokenError,
 UserIdIsNotExistError,
)
# Create your views here.


class Register(APIView):
 def get(self, request):
 return render(request, 'register.html')

 def post(self, request):
 # print("request.data 的內(nèi)容: ", request.data)
 # print("request.POST 的內(nèi)容: ", request.POST)

 # 針對(duì)數(shù)據(jù)輸入做檢查,是否符合規(guī)則
 ra_form = RegisterAccountForm(request.POST)
 if ra_form.is_valid():
  # print("驗(yàn)證過的數(shù)據(jù):", ra_form.cleaned_data)
  rua = RegisterUserAccount(request=request, **ra_form.cleaned_data)
  try:
  rua.execute()
  except UsernameAlreadyExistError as err:
  # 這里就是捕獲自定義異常,并給form對(duì)象添加一個(gè)錯(cuò)誤信息,并通過模板渲染然后返回前端頁面
  ra_form.add_error('username', str(err))
  return render(request, 'register.html', {'info': ra_form.errors})

  return HttpResponse('We have sent you an email, please confirm your email address to complete registration')
  # return HttpResponseRedirect("/account/login/")
 else:
  return render(request, 'register.html', {'info': ra_form.errors})


class Login(APIView):
 def get(self, request):
 return render(request, 'login.html')

 def post(self, request):
 print("request.data 的內(nèi)容: ", request.data)
 print("request.POST 的內(nèi)容: ", request.POST)
 pass


class ActivateAccount(APIView):
 # 用戶激活賬戶
 def get(self, request, uidb64, token):
 try:
  # 獲取URL中的用戶ID
  uid = force_bytes(urlsafe_base64_decode(uidb64))
  # 激活用戶
  aua = ActivateUserAccount(uid, token)
  aua.execute()
  return render(request, 'login.html')
 except(ActivatitionTokenError, UserIdIsNotExistError) as err:
  return HttpResponse('Activation is failed.')

這里就是視圖層不同URL由不同的類來處理,這里只做基本的接收輸入和返回輸出功能,至于接收到的輸入該如何處理則有其他組件來完成,針對(duì)輸入格式規(guī)范則由forms中的類來處理,針對(duì)數(shù)據(jù)驗(yàn)證過后的具體業(yè)務(wù)邏輯則由services中的類來處理。

Urls

from django.urls import path, re_path, include
from .views import (
 Register,
 Login,
 ActivateAccount,
)


app_name = 'users'
urlpatterns = [
 re_path(r'^register/$', Register.as_view(), name='register'),
 re_path(r'^login/$', Login.as_view(), name='login'),
 re_path(r'^activate/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
  ActivateAccount.as_view(), name='activate'),
]

Templates

是我用到的html模板,我就不放在這里了

下載全部的代碼

頁面效果

激活郵件內(nèi)容

點(diǎn)擊后就會(huì)跳轉(zhuǎn)到登陸頁。下面我們從Django admin中查看,2個(gè)用戶是激活狀態(tài)的。

相關(guān)文章

  • Python2實(shí)現(xiàn)的圖片文本識(shí)別功能詳解

    Python2實(shí)現(xiàn)的圖片文本識(shí)別功能詳解

    這篇文章主要介紹了Python2實(shí)現(xiàn)的圖片文本識(shí)別功能,結(jié)合實(shí)例形式分析了Python pytesser庫的安裝及使用pytesser庫識(shí)別圖片文字相關(guān)操作技巧,需要的朋友可以參考下
    2018-07-07
  • 樸素貝葉斯Python實(shí)例及解析

    樸素貝葉斯Python實(shí)例及解析

    這篇文章主要為大家詳細(xì)介紹了樸素貝葉斯Python算法實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • python?NetworkX庫生成并繪制帶權(quán)無向圖

    python?NetworkX庫生成并繪制帶權(quán)無向圖

    這篇文章主要為大家介紹了python?NetworkX庫生成并繪制帶權(quán)無向圖的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • python制作爬蟲并將抓取結(jié)果保存到excel中

    python制作爬蟲并將抓取結(jié)果保存到excel中

    本文給大家記錄的是使用Python制作爬蟲爬取拉勾網(wǎng)信息并將結(jié)果保存到Excel中的實(shí)現(xiàn)思路及方法,并附上最終源碼,有需要的小伙伴可以參考下
    2016-04-04
  • python變量命名的7條建議

    python變量命名的7條建議

    這篇文章主要介紹了python變量命名的7條建議,如何給變量命名,如何讓它變得有意義成了程序員不可逾越的難題,需要的朋友可以參考下
    2019-07-07
  • python數(shù)字圖像處理之基本形態(tài)學(xué)濾波

    python數(shù)字圖像處理之基本形態(tài)學(xué)濾波

    這篇文章主要為大家介紹了python數(shù)字圖像處理之基本形態(tài)學(xué)濾波示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • python redis 批量設(shè)置過期key過程解析

    python redis 批量設(shè)置過期key過程解析

    這篇文章主要介紹了python redis 批量設(shè)置過期key過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • python list count統(tǒng)計(jì)個(gè)數(shù)的實(shí)現(xiàn)

    python list count統(tǒng)計(jì)個(gè)數(shù)的實(shí)現(xiàn)

    這篇文章主要介紹了python list count統(tǒng)計(jì)個(gè)數(shù)的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • 利用Pandas索引和選取數(shù)據(jù)方法詳解

    利用Pandas索引和選取數(shù)據(jù)方法詳解

    使用Pandas做數(shù)據(jù)分析的時(shí)候,用的最多的功能恐怕就是對(duì)于數(shù)據(jù)集的索引,選組數(shù)據(jù)子集。Pandas庫提供了很多非常實(shí)用的方法,了解并熟練使用這些方法而不是用for循環(huán)的方法將會(huì)事半功倍。在這一篇文章中,我們將著重介紹這些方法
    2021-10-10
  • Python?Base64編碼和解碼操作

    Python?Base64編碼和解碼操作

    Base64?就是一種基于64個(gè)可打印字符來表示二進(jìn)制數(shù)據(jù)的方法,這篇文章主要介紹了Python?Base64編碼和解碼,需要的朋友可以參考下
    2022-12-12

最新評(píng)論

澜沧| 云南省| 淅川县| 贡山| 曲阜市| 丰顺县| 外汇| 泰兴市| 龙门县| 扶风县| 东乡| 富川| 集安市| 如皋市| 南召县| 漳州市| 诸城市| 德清县| 赣州市| 南京市| 阿拉善右旗| 乐昌市| 邵阳县| 湖北省| 光山县| 宝山区| 顺平县| 大埔区| 富源县| 宜春市| 太谷县| 汉中市| 罗山县| 天峻县| 婺源县| 桦南县| 嘉义县| 民权县| 汉中市| 鄂托克旗| 吉木乃县|