1. 程式人生 > >三【用django2.0來開發】會員註冊登錄

三【用django2.0來開發】會員註冊登錄

技術 復雜 html ffffff processor emp phone 表單元 jpg

github地址:https://gitee.com/ccnv07/django_example

本章主要講如何實現會員的前臺註冊登錄, 會涉及到以下模塊

  1. 簡單的路由設置
  2. 簡單的模板操作
  3. 視圖以及session的操作
  4. 比較復雜的表單
  5. ajax請求以及json返回資源

使用的還是上一節的model即可

實現註冊功能

# account/forms.py
class AccountForm(forms.ModelForm):
    # ... 忽略代碼
    def get_first_error(self):
        # 獲取所有錯誤轉換成的json格式
        errors = self.errors.get_json_data()
        if errors:
            for key, message_dicts in errors.items():
                # 存在錯誤則返回第一個錯誤信息, 返回string
                return message_dicts[0].get(‘message‘)

        return None

創建註冊表單

RegisterForm 會繼承AccountForm

# account/forms.py
class RegisterForm(AccountForm):
    # 設置場景是新增用戶
    # 這個不是django默認的, 是我自己加的, 作用是用來不同場景下的實現不同的自定義規則
    scene = ‘insert‘

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 因為前端框架使用的是bootstrap, 所以需要給所有的表單元素添加class名稱
        # 同樣的, 添加別的屬性也可以使用
        self.字段名.widget.attrs.update({‘html屬性名‘, ‘html屬性值‘})實現
        for _field in self.fields:
            self.fields[_field].widget.attrs.update({‘class‘: ‘form-control‘})

        # 因為在基類中設置的password字段是非必填項, 而在註冊的時候是必填的, 所以設置password的required屬性為True
        # 同時我們也不需要status字段, 所以設置為False
        self.fields[‘password‘].required = True
        self.fields[‘status‘].required = False

    class Meta(AccountForm.Meta):
        # 使用自定義的Form, 就必須指定fields or exclude屬性, 否則報錯
        # 只指定表單中藥用到的字段
        fields = (‘account‘, ‘password‘, ‘email‘, ‘phone‘)

    # 新增一個rep_password字段, 讓用戶輸入兩次密碼, 防止出錯
    rep_password = forms.CharField(
        label=‘重復密碼‘,
        required=True,
        error_messages={‘required‘: ‘請再次輸入密碼‘},
        widget=forms.PasswordInput())

    def clean_rep_password(self):
        # 驗證兩次輸入的密碼是否一致
        # 因為在clean_password方法中, 已經加密了cleaned_data[‘password‘], 所以這裏只能取data[‘password‘]
        if self.data[‘password‘] != self.cleaned_data[‘rep_password‘]:
            raise ValidationError(‘兩次輸入的密碼不一致‘)

        return self.cleaned_data[‘rep_password‘]

創建用戶模塊註冊的路由

先在account目錄中創建一個urls.py文件
然後在cms.urls中加載account中的urls.py文件

from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path(‘admin/‘, admin.site.urls),
    # include方法參數是urls文件的加載地址, 名字可以自己指定
    path(‘account/‘, include(‘account.urls‘))
]

然後再account.urls.py中創建登錄路由

# account/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # 訪問的url路徑是http://xxx/account/register
    # 把請求轉發給register方法
    # 這個路由的名稱是account-register
    path(‘register/‘, views.register, name=‘account-register‘),
]

註冊的具體邏輯

基本流程:

  1. 把數據放入form表單中
  2. 驗證表單
  3. 將表單中的數據保存到數據庫
  4. 成功/失敗都返回指定資源
# account/views.py
from django.views.decorators.http import require_http_methods
from .forms import RegisterForm
from cms.utils import return_json

# 指定可以請求的方式
@require_http_methods([‘GET‘, ‘POST‘])
def register(request):
    if request.method == ‘POST‘:
        # 把所有POST的數據都放入表單中
        form = RegisterForm(request.POST)
        # 驗證表單中的數據是否正確
        if form.is_valid():
            # 將數據保存到數據庫
            form.save()
            # 操作成功, 將數據返回給瀏覽器
            return return_json(url=reverse(‘account-index‘))
        else:
        # 驗證失敗, 從form中獲取到第一個錯誤信息, 返回給瀏覽器
            return return_json(code=1, message=form.get_first_error())
    else:
        form = RegisterForm()
        return render(request, ‘account/register.html‘, {‘form‘: form})

在這個方法中加載了一個return_json的方法, 具體代碼:

# cms/utils.py
from django.http import JsonResponse

def return_json(code = 0, message = ‘success‘, data = [], url=‘‘):
    return JsonResponse({
        ‘code‘: code,
        ‘url‘: url,
        ‘message‘: message,
    })

這個代碼的意思是返回一個Json的資源給瀏覽器解析

插播一個關於模板的配置

django默認的模板目錄在模塊/templates中
比如account模塊的模板目錄就是在account/templates
而在一般的開發過程中會把所有模板放在一起, 所以需要修改配置文件, 指定模板目錄的路徑到根目錄下。

# cms/settings.py
TEMPLATES = [
    {
        ‘BACKEND‘: ‘django.template.backends.django.DjangoTemplates‘,
        ‘DIRS‘: [
            # 將templates目錄放在根目錄, 也就是cms/templates中
            os.path.join(BASE_DIR, ‘templates‘),
        ],
        ‘APP_DIRS‘: True,
        ‘OPTIONS‘: {
            ‘context_processors‘: [
                ‘django.template.context_processors.debug‘,
                ‘django.template.context_processors.request‘,
                ‘django.contrib.auth.context_processors.auth‘,
                ‘django.contrib.messages.context_processors.messages‘,
            ],
        },
    },
]

而且靜態資源(css,js,fonts,img)文件一般也放在根目錄下
還是在cms/settings.py中增加配置

STATIC_URL = ‘/static/‘ # 指定靜態文件的訪問url前綴
STATICFILES_DIRS = (‘static‘, ) # 指定靜態文件的目錄地址

這樣模板和靜態資源的新路徑就成了cms/templates, cms/static
樣式文件使用的是bootstrap. 大家可以提前下載, 並且提前放入static文件中
然後目錄就變為以下這樣

cms/
    templates/
    static/
        css/
        fonts/
        js/

創建註冊表單模板

在templates中創建layout.html 框架文件
layout.html中包含的是所有模板共用的地方, 並且通過block標簽占位符來占位, 別的模板可以繼承layout.html, 然後修改block標簽的占位符中的內容, 可以大幅度減少代碼量

# templates/layout.html
<!-- 加載static模板, 作用是為了使用 {% static ‘filepath‘%} 來加載靜態資源-->
{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <!-- block名稱為title的占位符-->
    <title>{% block title %} {% endblock %}</title>
    <!-- 完全的文件地址是static/css/bootstrap.min.css-->
    <link rel="stylesheet" href="{% static ‘css/bootstrap.min.css‘%}">
</head>

<body>
    {% block body %} {% endblock %}
</body>
<script src="{% static ‘js/jquery.min.js‘ %}"></script>
<script src="{% static ‘js/bootstrap.min.js‘ %}"></script>
<script src="{% static ‘js/layer/layer.js‘ %}"></script>
<script src="{% static ‘js/utils.js‘ %}"></script>
</html>

註冊模板文件,templates/account/register.html

<!-- 繼承layout.html-->
{% extends ‘layout.html‘ %}
<!-- 將占位符名稱為title中的內容改為【註冊】-->
{% block title %} 註冊 {% endblock %}
{% block body %}
<div class="container">
    <div class="row" style="width:500px">
        <!-- url ‘路由的名稱, 就是urls中的name‘-->
        <form action="{% url ‘account-register‘%}" method="post" onsubmit="return post(this)">
            {% csrf_token %}
            <div class="form-group">
                <label for="{{ form.account.id_for_label}}">{{ form.account.label}}</label> {{ form.account}}
            </div>
            <div class="form-group">
                <label for="{{ form.password.id_for_label}}">{{ form.password.label}}</label> {{ form.password}}
            </div>
            <div class="form-group">
                <label for="{{ form.rep_password.id_for_label}}">{{ form.rep_password.label}}</label> {{ form.rep_password}}
            </div>
            <div class="form-group">
                <label for="{{ form.email.id_for_label}}">{{ form.email.label}}</label> {{ form.email}}
            </div>
            <div class="form-group">
                <label for="{{ form.phone.id_for_label}}">{{ form.phone.label}}</label> {{ form.phone}}
            </div>
            <input type="submit" value="提交" class="btn btn-success">
        </form>
    </div>
</div>
{% endblock %}

關於form的重點說明
form.字段名是訪問到字段
form.字段名.id_for_label, 是返回字段的id
form.字段名.label, 是label名稱
form.字段名 會直接返回表單元素的html
比如form.account就會變成&lt;input type="password" name="password" maxlength="12" minlength="6" class="form-control" required id="id_password" /&gt;

代碼中return post(this)的js function代碼如下

# static/js/utils.js
function post(form) {
    $.ajax({
        url: $(form).attr(‘action‘),
        method: ‘POST‘,
        dataType: ‘json‘,
        data: $(form).serialize(),
        success: function(data) {
            if (data.code == 0) {
                layer.msg(‘操作成功‘);
                window.location.href = data.url;
            } else {
                layer.msg(data.message);
            }
        }
    })
    return false;
}

啟動開發服務器並測試

python manager.py runserver

技術分享圖片

實現登錄方法

實現登錄的方法和註冊的流程是一樣的, 可以自己嘗試實現以下, 如果有難度可以對照我的代碼

# account/forms.py
class LoginForm(AccountForm):
    scene = ‘login‘

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for _field in self.fields:
            self.fields[_field].widget.attrs.update({‘class‘: ‘form-control‘})

        # 設置密碼為必須輸入項
        self.fields[‘password‘].required = True
        self.fields[‘email‘].required = False
        self.fields[‘phone‘].required = False
        self.fields[‘status‘].required = False

    class Meta(AccountForm.Meta):
        # 使用自定義的Form, 就必須指定fields or exclude屬性, 否則報錯
        fields = (‘account‘, ‘password‘)
# account/views.py
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.http import require_http_methods
from django.shortcuts import redirect
from .forms import RegisterForm, LoginForm
from .utils import authenticate, login_required
from cms.utils import return_json
from functools import wraps

@login_required(login_url=‘/account/login/‘)
def index(request):
    return render(request, template_name=‘account/index.html‘)

@require_http_methods([‘GET‘, ‘POST‘])
def login(request):
    if request.method == ‘POST‘:
        form = LoginForm(request.POST)

        if form.is_valid():
            user = authenticate(
                request,
                account=form.cleaned_data[‘account‘],
                password=form.cleaned_data[‘password‘])

            if user is not None:
                return return_json(url=reverse(‘account-index‘))
            else:
                return return_json(code=1, message=‘賬號或密碼不正確‘)
        else:
            return return_json(code=1, message=form.get_first_error())
    else:
        form = LoginForm()
        return render(request, ‘account/login.html‘, {‘form‘: form})
# account/utils.py
from .models import Account
from django.contrib.auth.hashers import check_password
from django.shortcuts import redirect
from functools import wraps

def authenticate(request, account, password):
    try:
        user = Account.objects.get(account=account)
    except Account.DoesNotExist:
        return None

    if not check_password(password, user.password):
        return None

    request.session[‘user_id‘] = user.id
    request.session[‘account‘] = user.password

    return user

def login_required(func=None, login_url=‘‘):
    def wrapper(func):
        @wraps(func)
        def _func(request, *arg, **kwargs):
            if request.session.get(‘user_id‘):
                return func(request, *arg, **kwargs)
            else:
                return redirect(login_url)

        return _func
    return wrapper

request.session就是一個session對象, 可以用來保存用戶登錄後的信息, 使用方法和dict完全一樣。

技術分享圖片

三【用django2.0來開發】會員註冊登錄