五 【用django2.0来开发】实现会员注册功能

2023-05-06,,

上一节我们完成了会员功能的后台管理, 这一节我们需要完成会员注册功能, 涉及到以下几个模块

  1. URL配置
  2. views视图模块
  3. 模板
  4. Request/Response对象的使用

项目地址:https://gitee.com/ccnv07/django_example

URL路由配置

django是通过项目的urls.py文件来定义网站的url路由, 在我们的项目中是cms/urls.py文件

django的基本访问流程

  1. 访问url时, 通过cms/urls.py中定义的url路由, 获取到要执行的视图函数或者类(保存在views.py)
  2. 将Request(请求对象)转发到指定的视图函数/类中, 执行视图函数/类的代码
  3. 通过模板渲染(没有模板则是其他的JsonResponse等资源对象), 将结果数据返回并输出到浏览器中

打开cms/urls.py文件

from django.contrib import admin
from django.urls import path
urlpatterns = [
    path('admin/', admin.site.urls),
]

urlpatterns是整个路由的一个list, 是django定义的固定名称

path函数
path(route, view, kwargs=None, name=None)
route: 指定访问的路由
view: 是指定访问的视图函数/类
name: 是指定这条路由的名称, 通过这个名称, 我们就可以生成一个可访问的url

默认的这一条路由, 就是定义了整个后台的访问url, 都是以admin/开头的url, django会将admin.site.urls中定义的路由都加载过来

比如我们之前做的后台管理功能, url就是:/admin/account/account/

路由route参数的格式

1. 固定字符串路由

这种路由是固定访问的url, 不会发生变化, 比如关于我的访问页面.

urlpatterns = [
    path('about/', views.about),
]
2. 带有变量的url路由, 比如我们访问指定栏目下的文章
urlpatterns = [
    path('list/<int:nav_id>', views.list),
]

这种应该用会比较多一些, <int:>指定这个nav_id必须是数字类型, 会执行类型强制转换, 而nav_id就是参数名, 通过以下的方式, 就可以访问到这个参数。

# views.py
def list(request, nav_id):
    pass

除了支持int, django的url路由也支持str,slug,uuid,path四种类型, 一般常用的也就是str和int

3. 正则表达式路由
from django.urls import path, re_path

from . import views

urlpatterns = [
    path('articles/2003/', views.special_case_2003),
    re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
]

path函数定义的是普通的路由
re_path韩都定义正则路由, 参数完全一样

像上面这个例子中的re_path, 每个()中就是一个参数的定义, ?P说明这里定义的是一个参数, <year>是参数key, [0-9]{4}是正则表达式, $符代表路由结束, 不再往后匹配。
所以这个url可以匹配到articles/2018/ 这样的url

urlpatterns = [
    re_path(r'^comments/(?:page-(?P<page_number>\d+)/)?$', comments),  # good
]

在这个例子中, ?: 代表是这是一个字符串的url, page-并不是一个参数
所以匹配的url是comments/page-1 的url

4. 包含其他的路由
from django.urls import include, path
urlpatterns = [
    path('account/', include('account.urls')),
]

include
include(module, namespace=None)
include(pattern_list)
include((pattern_list, app_namespace), namespace=None)

module: urlconf的模块
namespace: url入口的命名空间
pattern_list: 可以迭代的path()/re_path实例类
app_namespace: url入口的app的命名空间

之后会依次讲, 一般我们常见的也就是include('account.urls'))这种方式, 会将account/urls.py中定义的url路由都加载进来
一下就是在account/urls.py中定义的路由

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

urlpatterns = [
    path('index/', views.index, name='account-index')
]

根据以上的路由规则, 我们可访问的url就是account/index/ 这个url

定义会员注册的路由

将模块路由文件加载进项目中

# cms/urls.py
import debug_toolbar
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('account.urls'))
]

这样关于会员模块的url路由, 我们就都可以在account/urls.py文件中定义了。

在模块中定义url路由

创建account/urls.py文件

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

urlpatterns = [
    path('register/', views.register, name='account-register')
]

View视图模块

视图函数的定义

from django.http import HttpResponse
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

每个视图函数都有一个固定的参数request, 这个是Request对象, 包含浏览器发起请求带的参数, 包括常见的url, post数据, 请求类型, header头等等。

然后视图函数的返回也必须是一个Reponse对象, 一般我们返回的都是html代码, 所以使用的是HttpResponse对象
有时候我们写的是接口, 使用的是JsonResponse对象

视图中最常用的函数

render 模板渲染函数

但是如果把html代码写在python文件中, 也太不好看了, 所以, 我们可以通过render函数来完成模板的渲染
render(request, template_name, context=None, content_type=None, status=None, using=None)

request: 是请求对象,
template_name: 模板文件名称
context: 要传递给模板文件的变量
content_type: 文档header头中Content-Type的类型
status: http响应码, 就是200, 301, 302, 400, 500那个
using: 要使用的模板引擎

from django.shortcuts import render

def my_view(request):
    # View code here...
    return render(request, 'myapp/index.html', {
        'foo': 'bar',
    }, content_type='application/xhtml+xml')

render会自动帮我们转换成HttpResponse对象, 所以也不需要再写一遍HttpResponse了

redirect 跳转函数

当会员注册完成后, 我们就需要自动跳转到会员中心页或者首页, 这时就得使用redirect函数来实现了
redirect(to, permanent=False, *args, **kwargs)

to: 要跳转的地址,
permanent: 是否永久跳转, 说白了就是301/302代码的区别, 不动301,302自行百度。

from django.shortcuts import redirect
def my_view(request):
    ...
    return redirect('/some/url/')
reverse Url路由转url函数

redirect完成url跳转时, 万一我定义的url都变了, 觉得以前定义的url太丑了, 太长了, 老板不喜欢了, 那不坑爹了么?那我不得满项目找url, 挨个改阿?
其实django已经想到这点了, 还记得我们在定义url路由时的name参数么?
通过reverse函数, 就可以将urls.py中定义的url路由转换为url了

reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)
viewname: url路由的名称
urlconf: url路由的模块名, 默认是根模块, 也就是咱们的cms文件中的urls.py
args: 要传递给url路由的参数

视图中的Request和Response对象

一般发生请求后有两种资源, 一种是请求的资源,是浏览器发送给服务器的资源, 包括请求的url, 头, 传递的参数, cookie什么的。
还有一种是返回的资源, 就是服务器发送给浏览器的资源。

HttpRequest对象

常用的属性如下

属性 说明
scheme http 或 https
body 请求的主体
path 请求的路径 account/register/
path_info 请求的路径
method 请求方法GET,POST
encoding 编码类型
content_type header头 的Content-Type
COOKIES cookie信息
FILES 表单的file字段上传的文件信息
META header头信息
session 保存session信息, dict结构

常用的方法

方法 说明
get_host() 127.0.0.1:8000
get_port() 请求的端口
get_full_path() 请求的全路径
is_ajax() 是否ajax请求
HttpResponse对象

常用的属性

属性 说明
content 请求返回的资源内容
charset 编码
status_code 返回的http状态码
JsonResponse对象

JsonResponse(data, encoder=DjangoJSONEncoder, safe=True, json_dumps_params=None, **kwargs)

data: 要返回的json数据, 是dict结构
encoder: 数据的转码类, 一般不需要更改
json_dumps_params: json_dumps函数

针对我们的项目, 就可以在cms/utils.py(没有就创建)文件中定义一个通用的返回jsonResponse的函数,

from django.http import JsonResponse

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

模板层说明

模板文件路径

默认模板文件路径会在模块名/templates中, 但是在一般的项目开发中, 都会把所有的模板放在一起, 所以我们需要重新定义模板的路径

# cms/settings.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            # 将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',
            ],
        },
    },
]

在settings.py 中TEMPLATES.DIRS中增加os.path.join(BASE_DIR, 'templates'), 模板文件的目录就变为了cms/templates

静态资源文件路径和访问url修改

静态文件路径同模板一样, 我们也需要修改到根目录下

# cms/settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = ('static', )

模板常用标签说明

一般模板的顶部、底部等很多地方都是一样的, 所以我们可以定义一个布局html页面, 将这些一样的地方提取出来放在一起

# templates/layout.html
{% 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">
    <title>{% block title %} {% endblock %}</title>
    <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>

load static 这个标签是指加载static模块, 只有加载了后, 才可以使用{% static %}来读取静态资源文件
block endblock 定义了不同的块, 并且为每个块进行命名
这样假设我定义了一个会员注册页

# templates/account/register.html
{% extends 'layout.html' %}
{% block title %} 注册 {% endblock %}

那么, layout.html中的{% block title %} {% endblock %}就会被替换成"注册"

extends 标签, 指定的就是加载layout.html这个布局页面

定义注册会员的表单

我们先在account/forms.py中定义表单RegisterForm, 因为之前已经定义了一个AccountForm, 所以我们这个表单可以直接继承AccountForm

class RegisterForm(AccountForm):
    # 设置场景是新增用户
    scene = 'insert'
    class Meta(AccountForm.Meta):
        # 使用自定义的Form, 就必须指定fields or exclude属性, 否则报错
        # 注册的时候我们不需要设置status, 字段, 所以排除掉。
        fields = ('account', 'password', 'email', 'phone')

注册的时候, 一般需要输入重复密码, 所以我们多定义一个rep_password字段

class RegisterForm(AccountForm):
    ... 忽略代码
     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']

定义视图

在视图中, 如果是GET请求, 我们则渲染表单, 如果是POST请求, 我们就执行注册用户

GET 请求的代码

from django.shortcuts import render
from .forms import RegisterForm

def register(request):
    form = RegisterForm()
        return render(request, 'account/register.html', {'form': form})

编写模板代码

我使用的是bootstrap前端框架, 大家可以下载了放在static文件夹中, 修正layout.html中的路径

首先我们先将layout.html布局模板加载进来

# templates/account/register.html
{% extends 'layout.html' %}
{% block title %} 注册 {% endblock %}

然后在block body部分, 写入我们要渲染的表单

{% block body %}
<div class="container">
    <div class="row" >
        <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就是我们在view中传过来的form表单类
form.account_id_for_label: 就是input的类
{{ form.account.label}}: 是显示的表单字段的名称
{{ form.account}}: 会直接生成一段input的表单字段代码。

打开浏览器, 我们可以看一下效果

看起来样式还不错
现在, 我们就可以点击提交尝试一下了