开发环境:

  • Microsoft Windows 10 Enterprise LTSC [Version 10.0.19044.1586], locale zh-CN

  • Python 3.8.10

  • PyCharm 2021.2 (Professional Edition)

  • Visual Studio Code, 64-bit edition (version 1.67.2)


修改网站的导航栏

打开 templates/nav.html 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!-- 导航菜单栏 -->
<nav class="navbar navbar-expand-sm bg-light mb-4">
<div class="container d-flex w-100">
<h2>我的网站</h2>
<div class="flex-grow-1"></div>
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="/">首页</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/department/">组织构架管理</a>
</li>
<li class="nav-item">
<div class="nav-link" href="#">
{% if request.user.is_authenticated %}
当前用户: {{ request.user.username }}
{% endif %}
</div>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout/">注销 </a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login/">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup/">注册</a>
</li>
</ul>
</div>
</nav>

扩展用户模型

Djano 已经内置有用户管理的模型, 但是其原有字段可能不适合于当前的应用, 所以对用户模型进行扩展。

添加应用 users

打开 PowerShell窗口, 激活虚拟环境, 创建名为 uers 的应用

1
2
venv\Scripts\activate
python manage.py startapp users

打开 users/models.py 文件, 添加 以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from django.db import models
from django.contrib.auth.models import AbstractUser
from department.models import Department


class UserProfile(AbstractUser):
"""
用户: makemigration提示错误:sers.UserProfile.user_permissions: (fields.E304),
需要在settings中指定自定义认证模型:AUTH_USER_MODEL = 'users.UserProfile'
"""
name = models.CharField(
max_length=20,
default='',
verbose_name='姓名')
gender = models.CharField(
max_length=10,
choices=(('male', '男'), ('female', '女')),
default='male',
verbose_name='性别')
mobile = models.CharField(
max_length=11,
default='',
verbose_name='电话')
department = models.ForeignKey(
Department,
blank=True,
null=True,
verbose_name='所属部门',
on_delete=models.CASCADE)
position = models.CharField(
max_length=50,
blank=True,
null=True,
verbose_name='职位')
enrollment_date = models.DateField(
blank=True,
null=True,
verbose_name='入职日期')

class Meta:
verbose_name = '用户信息'
verbose_name_plural = verbose_name
ordering = ['id']

def __str__(self):
return self.name

AbstractUser: Django 内置的一个完整用户模型,包含字段,作为一个抽象类,以便您可以继承它并添加您自己的配置文件字段和方法。

AbstractBaseUser: 仅包含身份验证功能,但不包含实际字段:当您继承子类时,您必须提供它们

修改项目settings.py

打开 myproject/settings.py, 添加自定义的用户模型

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'department',
'users', # 安装新添加的应用
]

AUTH_USER_MODEL = 'users.UserProfile' # 指定自定义认证模型

如下图所示

迁移模型

manage.py 所在文件夹打开 PowerShell窗口, 激活虚拟环境, 执行以下两个命令:

1
2
3
python manage.py makemigrations

python manage.py migrate

迁移的时候有可能会出现类似 ValueError: Related model 'departments.structure' cannot be resolved 的错误。

解决办法:1. 删除 各个应用migrations 文件夹除 __init__.py以外的文件, 重新执行以上两个迁移的命令;

如果还不行,则删除数据库, 然后重建同名数据库, 重新执行以上两个迁移的命令;

重建数据后, 要重新创建超级用户(如果有需要) 及添加数据

删除 迁移相应的文件

删除数据库文件

命令执行过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(venv) PS D:\PycharmProjects\djangoProject> python manage.py makemigrations
No changes detected
(venv) PS D:\PycharmProjects\djangoProject> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, department, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0001_initial... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying department.0001_initial... OK
Applying sessions.0001_initial... OK

基于类的视图

添加用户相关的视图文件

  1. templates 文件夹中新建名为 users(与新添加的应用同名) 的文件夹

  2. templates/users 文件夹新建用于 用户注册用户登录 的网页文件:signup.htmllogin.html 待用

添加用户 注册、登录 的表单类

users 文件夹新建名为 forms.pyPython file, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from django import forms
from django.core import validators
from django.contrib.auth import get_user_model
from .models import UserProfile


# get_user_model()实际获取的是settings.AUTH_USER_MODEL指定的User model
User = get_user_model()


class LoginForm(forms.Form):
username = forms.CharField(
required=True,
widget=forms.TextInput(attrs={'class': 'form-control'}),
error_messages={"required": "请填写用户名"})
password = forms.CharField(
required=True,
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
error_messages={"required": u"请填写密码"})


class UserUpdateForm(forms.ModelForm):
class Meta:
model = User
fields = ['name', 'gender', 'username', 'mobile', 'email', 'enrollment_date',
'department', 'position', 'is_active']


class UserCreateForm(forms.ModelForm):
"""
创建用户表单,进行字段验证
"""

username = forms.CharField(
required=True,
min_length=3,
widget=forms.TextInput(attrs={'class': 'form-control'}),
error_messages={"required": "请填写用户名",
"min_length": "密码长度最少6位数", })
email = forms.CharField(
required=True,
validators=[validators.EmailValidator()],
widget=forms.TextInput(attrs={'class': 'form-control'}),
error_messages={"required": "请填写邮箱", })

password = forms.CharField(
required=True,
min_length=6,
max_length=20,
widget=forms.PasswordInput(attrs={'class': 'form-control'}),
error_messages={"required": u"密码不能为空",
"min_length": "密码长度最少6位数", })

# confirm_password = forms.CharField(
# required=True,
# min_length=6,
# max_length=20,
# widget=forms.PasswordInput(attrs={'class': 'form-control'}),
# error_messages={"required": u"确认密码不能为空",
# "min_length": "密码长度最少6位数", })

class Meta:
model = User
fields = ['password', 'username', ]

编辑视图

打开 users/views.py 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.views.generic.base import View
from django.contrib.auth import authenticate, login, logout
from .forms import LoginForm, UserCreateForm
from .models import UserProfile


# 基于类的视图

class SignupView(View):
"""
注册新用户
"""

def get(self, request): # 处理 get 请求的函数
signup_form = UserCreateForm()
return render(request, 'users/signup.html', locals())

def post(self, request): # 处理 post 请求的函数
signup_form = UserCreateForm(request.POST)
if signup_form.is_valid():
cd = signup_form.cleaned_data
# 创建用户
new_user = UserProfile.objects.create_user(cd['username'], cd['email'], cd['password'])
# 设置 name 和 username 相同
new_user.name = new_user.username
new_user.save()
login(request, new_user) # 注册成功后直接登录该用户(可选)
return HttpResponseRedirect('/')
else:
error_msg = '注册失败'
return render(request, 'users/signup.html', locals())


class LoginView(View):
"""
用户登录
"""

def get(self, request): # 处理 GET 请求的函数
login_form = LoginForm()
return render(request, 'users/login.html', locals())

def post(self, request): # 处理 POST 请求的函数
# 用于登录成功后返回进入登录界面的前一页
redirect_to = request.GET.get('next', '/')
login_form = LoginForm(request.POST)
if login_form.is_valid():
cd = login_form.cleaned_data
_username = cd['username']
_password = cd['password']
# 验证用户登录
user = authenticate(username=_username, password=_password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect(redirect_to)
else:
error_msg = '用户未激活'
return render(request, 'users/login.html', locals())
else:
error_msg = "用户名或密码错误!"
return render(request, 'users/login.html', locals())
else:
error_msg = "用户名和密码不能够为空!"
return render(request, 'users/login.html', locals())


class LogoutView(View):
"""
退出登录
"""

def get(self, request):
logout(request)
return HttpResponseRedirect('/')

添加路由

用户 注册登录注销这几个操作不想添加有前缀的url, 所以直接将相关的路由添加到项目的 urls.py 文件中。打开 djangoProject/urls.py 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.contrib import admin
from django.urls import path, include
import department.views
from users.views import LoginView, LogoutView, SignupView # 导入视图

urlpatterns = [
path('admin/', admin.site.urls),
path('department/', include('department.urls')),
path('', department.views.home),

# 用户注册、登录相关的路由
path('login/', LoginView.as_view(), name='login'),
path('logout/', LogoutView.as_view(), name='logout'),
path('signup/', SignupView.as_view(), name='signup'),
]

注册和登录的模板

编辑用于用户注册登录的网页文件

注册用户的模板

打开 templates/users/signup.html 文件, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
{% extends "layout.html" %}
{% block title %}用户注册{% endblock %}
{% block content %}

<div class="d-flex justify-content-center align-items-center vh-100 bg-secondary">
<div class="card">
<h3 class="text-center p-3">注册新用户</h3>
<form method="POST" class="bg-white login_form">
{% csrf_token %}
<div class="text-danger text-center"> {{ error_msg }}</div>
<div class="row align-items-center g-3 m-3">
<div class="col-3 d-flex justify-content-end">
<label for="username" class="col-form-label">用户名</label>
</div>
<div class="col-9">
{{ signup_form.username }}
</div>
</div>

<div class="row align-items-center g-3 m-3">
<div class="col-3 d-flex justify-content-end">
<label for="email" class="col-form-label">电子邮箱</label>
</div>
<div class="col-9">
{{ signup_form.email }}
</div>
</div>
<div class="row align-items-center g-3 m-3">
<div class="col-3 d-flex justify-content-end">
<label for="password" class="col-form-label">密码</label>
</div>
<div class="col-9">
{{ signup_form.password }}
</div>
</div>

<div class="row align-items-center g-3 m-3">
<button type="submit" class="btn btn-primary block">注册新用户</button>
</div>
<div class="text-end g-3 m-3">
<a class="text-decoration-none mx-3 text-success" href="/">返回首页</a>
<a class="text-decoration-none" href="/login/">使用已有账号登录</a>
</div>
</form>
</div>
</div>

{% endblock %}


{% block style %}
<style>
.login_form {
width: 480px;
}
</style>
{% endblock %}

注册新用户的界面

用户登录的模板

打开 templates/users/login.html, 编辑其内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{% extends "layout.html" %}
{% block title %}用户登录{% endblock %}
{% block content %}

<div class="d-flex justify-content-center align-items-center h-100 bg-secondary">
<div class="card">
<h3 class="text-center p-3">用户登录</h3>
<form method="POST" class="bg-white login_form">
{% csrf_token %}
<div class="text-danger text-center"> {{ error_msg }}</div>
<div class="row align-items-center g-3 m-3">
<div class="col-3 d-flex justify-content-end">
<label for="username" class="col-form-label">用户名</label>
</div>
<div class="col-9">
{{ login_form.username }}
</div>
</div>
<div class="row align-items-center g-3 m-3">
<div class="col-3 d-flex justify-content-end">
<label for="password" class="col-form-label">密码</label>
</div>
<div class="col-9">
{{ login_form.password }}
</div>
</div>
<div class="row align-items-center g-3 m-3">
<button type="submit" class="btn btn-primary block">登录</button>
</div>
<div class="text-end g-3 m-3">
<a class="text-decoration-none mx-3 text-success" href="/">返回首页</a>
<a class="text-decoration-none" href="/signup/">没有账号</a>
</div>
</form>
</div>
</div>

{% endblock %}


{% block style %}
<style>
.login_form {
width: 480px;
}
</style>
{% endblock %}


用户登录的界面

启用登陆验证

Django 在做后台系统过程中,我们通常都会为 视图函数 添加 @login_required 装饰器,这个装饰器的主要作用就是在用户访问这个方法时,检查用户是否已经成功登陆,如果没有则重定向到登陆页面。

打开 department/views.py 文件, 编辑部分内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 省略原有的部分代码
from django.contrib.auth.decorators import login_required # 导入登录验证

@login_required(login_url='/login/')
def create(request):
"""
省略部分代码
"""

@login_required(login_url='/login/')
def update(request, dep_id):
"""
省略部分代码
"""

@login_required(login_url='/login/')
def delete(request, dep_id):
"""
省略部分代码
"""

在函数上方添加的 @ 开头的对象为装饰器,作用是给函数增加额外的功能

@login_required(login_url=’/login/‘): 必须登录后才能访问的装饰器

login_url=’/login/‘: 当未登录用户访问该函数时自动跳转到的登录url

参考资料:

  1. Django v4.0 中文文档

  2. Django入门与实践教程

  3. Bootstrap5 中文手册

===END===