目录:
- 1. 开发环境
- 2. 项目功能
- 3. 项目创建
- 4. 用户模型设计(数据库设计)
- 方案2 OneToOneField 方案的程序设计
- 方案4 **扩展 `AbstractUser`,创建自定义用户模型** 的程序设计
- 5. 登录页 业务逻辑设计
- 6. 注册页 业务逻辑设计
- 7. 邮箱 激活邮件设计
- on_delete
1. 开发环境
SystemOS: MacOS Monterey 12.3 ARM (M1 Pro)
Python: 3.9
Django: 4.0.3
Database: MySQL 8.0.28
IDE: Pycharm 2021.3.3(Professional Edition)
Package: mysqlclient 2.1.0
2. 项目功能
- 用户数据库
- 邮箱注册
- 激活邮件
- 用户登陆
3. 项目创建
假设 Django 项目已经创建。创建 用户登陆app(在本文中,以 UserProfile 为例)
python manage.py startapp UserProfile
setting.py中注册APP(UserProfile)
# UserProfile/apps.py
from django.apps import AppConfig
class UserProfileConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'UserProfile'
# /setting.py
INSTALLED_APPS = [
'UserProfile.apps.UserProfileConfig', # 注册该APP
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
4. 用户模型设计(数据库设计)
Django 的内置身份验证系统非常棒。在大多数情况下,我们可以开箱即用,节省大量开发和测试工作。它适合大多数用例,并且非常安全。但是有时我们需要做一些很好的调整来适应我们的 Web 应用程序。
通常,我们希望存储更多与用户相关的数据。您可能希望存储一个简短的个人信息、用户的地址,以及其他类似的用户信息。
在本节中介绍四种不同的策略来扩展默认的 Django 用户模型,本项目采用 扩展AbstractUser 方案。四种方案是指:
- 使用代理模型什么是代理模型?它是一种模型的继承,无需在数据库中创建新表。它用于在不影响现有数据库模式的情况下更改现有模型的行为(例如,默认排序、添加新方法等)。什么时候应该使用代理模型?当您不需要在数据库中存储额外的信息时,您应该使用代理模型来扩展现有的用户模型,但是我们需要添加额外的方法或更改模型的查询管理器。
- 使用 OneToOneField 方案什么是一对一链接?它是一个常规的Django模型,它将拥有自己的数据库表,并通过 OneToOneField 与现有的用户模型保持一对一的关系。我什么时候应该使用一对一的链接?当需要存储与身份验证过程无关的现有用户模型的额外信息时,应该使用一对一链接。我们通常称之为用户配置文件。
- 扩展
AbstractBaseUser
,创建自定义用户模型(通常情况下,不建议采用此方案)扩展 AbstractBaseUser 的自定义用户模型是什么?它是从 AbstractBaseUser 继承的一个全新的用户模型,并通过settings.py
引用。# /setting.py 中设定参数
需要注意的是,这样的操作,我们应该在项目的开始阶段设计好完,因为它操作将极大地影响数据库模式。执行时要格外小心。什么时候应该使用 AbstractBaseUser 的自定义用户模型?当您的应用程序对身份验证过程有特定的需求时,您应该使用自定义用户模型。例如,在某些情况下,使用电子邮件地址登录,或者是使用手机验证登录的时候。
AUTH_USER_MODEL = 'UserProfile.userprofile' - 扩展
AbstractUser
,创建自定义用户模型什么是 AbstractUser 自定义用户模型?它是一个从AbstractUser
继承的新用户模型。并通过settings.py
引用。# /setting.py 中设定参数
需要注意的是,这样的操作,我们应该在项目的开始阶段设计好完,因为它操作将极大地影响数据库模式。执行时要格外小心。什么时候应该使用扩展 AbstractUser 的自定义用户模型?当您对Django如何处理身份验证过程非常满意时,您应该使用它,并且您不会对它做任何更改。但你希望直接在用户模型中添加一些额外的信息,而不需要创建一个额外的类时(如选项2)。
AUTH_USER_MODEL = 'UserProfile.userprofile'
方案2 OneToOneField 方案的程序设计
方案4 扩展 AbstractUser
,创建自定义用户模型 的程序设计
# UserProfile/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings
class userprofile(AbstractUser):
"""
用户信息
通过 AbstractUser 继承 django 原有自带的 User 类
"""
gender_choices = (
('male', '男'),
('female', '女'),
)
username = models.CharField('用户名', max_length=50, null=False, blank=False)
birthday = models.DateField('生日', null=True, blank=True)
gender = models.CharField('性别', max_length=10, choices=gender_choices, default='female')
adress = models.CharField('地址', max_length=100, default='')
mobile = models.CharField('手机号', max_length=11, null=True, blank=True)
# 将 email 参数作为登陆的唯一验证
email = models.EmailField('邮箱', unique=True, null=True, blank=False)
USERNAME_FIELD = 'email'# 该语句将 email 作为用户认证的方式
REQUIRED_FIELDS = [] # 不添加这个Django会报错 email在REQUIRED_FIELDS中,不能作为USERNAME_FIELD
# Meta 类指出了 Django Admin 后台中显示的信息
class Meta:
verbose_name = '用户信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
class EmailVerifyRecord(models.Model):
"""
图形验证码
"""
send_choices = (
('register','注册'),
('forget','找回密码')
)
code = models.CharField('验证码',max_length=20)
email = models.EmailField('邮箱',max_length=50)
send_type = models.CharField(choices=send_choices,max_length=10)
send_time = models.DateTimeField()
class Meta:
verbose_name = '邮箱验证码'
verbose_name_plural = verbose_name
# pycharm terminal 执行迁移数据库操作
python manage.py makemigration
python manage.py migrate
对于之前已经创建了数据库,这边进行修改后迁移的时候,报各种奇怪错误的情况,将该 APP 下的 migrate 文件中除 __init__.py 的文件全部删除。再执行迁移操作。
对于操作后还出现奇奇怪怪错误的。先执行:
python manage.py makemigration UserProfile
然后执行:
python manage.py sqlmigrate UserProfile 0001
查看 Django 将执行的数据库操作
然后进入 MySQL 数据库后台,手动执行待执行的操作。
5. 登录页 业务逻辑设计
我的登陆页是 login.html,模版文件一般情况下放在根目录的 templates 文件夹中,css,js等静态文件可在根目录下新建static 文件夹,并在其下建立 App 文件夹放置静态文件。在 /setting.py 中设置静态文件文件路径,如何建立前端文件不再赘述。
# /setting.py
STATIC_URL = 'static/'# 该项为通过url搜索静态文件文件时需要的url前缀
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# 设置全局中查找通用静态文件的路径为 根目录/static/...
login.html 设置如下:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Login - Q-Query</title>
</head>
<body>
<div class="p-5">
<div class="text-center">
<h4 class="text-dark mb-4">Login!</h4>
</div>
<form class="user" method="post">
<div class="mb-3"><input type="email" placeholder="Enter Email Address..." name="email"></div>
<div class="mb-3"><input type="password" placeholder="Password" name="password"></div>
<div class="custom-control custom-checkbox small">
<input type="checkbox"><label for="formCheck-1">Remember Me</label>
</div>
<button type="submit">Login</button>
<hr>
{% csrf_token %}
</form>
<div class="text-center"><a class="small" href="forgot-password.html">Forgot Password?</a></div>
<div class="text-center"><a class="small" href="/qtd/register">Create an Account!</a></div>
</div>
<!--以下语句是用来弹窗消息的,需要在 views.py 中导入 from django.contrib import messages-->
{% if messages %}
<script>
{% for msg in messages %}
alert('{{ msg.message }}');
{% endfor %}
</script>
{% endif %}
</body>
</html>
在 UserProfile/views.py 中创建登录业务逻辑
# UserProfile/views.py
from django.shortcuts import render
from django.contrib.auth import authenticate
from django.contrib import messages
def login(request):
if request.method == 'POST':
# 获取用户提交的账号和密码
user_name = request.POST.get('email')
pass_word = request.POST.get('password')
print('user_name:', user_name)
print('pass_word:', pass_word)
# 成功返回user对象,失败None
user = authenticate(username=user_name, password=pass_word)
print(user)
# 如果不是null说明验证成功
if user isnotNone:
if user.is_active:
print('邮箱已激活,并且登录成功')
# 登录
return render(request, 'UserProfile/dashboard.html', {'name': user_name})
else:
print('邮箱未激活,登录失败')
messages.error(request, '邮箱未激活,登陆失败')
return render(request, 'UserProfile/login.html',)
else:
print('登录失败')
messages.error(request, '用户名或密码不正确')
return render(request, 'UserProfile/login.html',)
else:
return render(request, 'UserProfile/login.html',)
6. 注册页 业务逻辑设计
regesiter.html 设置如下:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<title>Register - Q-Query</title>
<link rel="stylesheet" href="{% static 'apps/bootstrap/css/bootstrap.min.css' %}">
</head>
<body class="bg-gradient-primary">
<div class="p-5">
<div class="text-center">
<h4 class="text-dark mb-4">Create an Account!</h4>
</div>
<form class="user", method="POST">
<div class="row mb-3">
<div><input type="text" placeholder="User Name" name="username"></div>
<div><input type="number" placeholder="Phone Number" name="phone_number"></div>
</div>
<div class="mb-3"><input type="email" placeholder="Email Address" name="email"></div>
<div class="row mb-3">
<div><input type="password" placeholder="Password" name="password"></div>
<div><input type="password" placeholder="Repeat Password" name="password_repeat"></div>
</div>
<button type="submit">Send Email Verification Code</button>
<hr>
<a role="button">Already have an account ? Login !</a>
<hr>
{% csrf_token %}
</form>
<div class="text-center"><a class="small" href="forgot-password.html">Forgot Password?</a></div>
<div class="text-center"><a class="small" href="/qtd/switch">Already have an account? Login!</a></div>
</div>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
</body>
</html>
在 UserProfile/views.py 中创建登录业务逻辑:
# UserProfile/views.py
from django.shortcuts import render
from UserProfile.models import userprofile
from django.contrib import messages
from django.contrib.auth.hashers import make_password
from UserProfile.utils.mail_send import send_register_email
def register(request):
if request.method == 'POST':
e_mail = request.POST.get('email')
username = request.POST.get('username')
mobile = request.POST.get('phone_number')
if Queriers.objects.filter(email=e_mail):
messages.error(request, '邮箱已存在')
return render(request, 'UserProfile/register.html',)
pass_word = request.POST.get('password')
print('e_mail:', e_mail)
print('pass_word:', pass_word)
# 实例化一个 userprofile 对象
user_profile = userprofile()
user_profile.username = username
user_profile.email = e_mail
user_profile.mobile = mobile
# 默认添加的用户是激活状态(is_active=1表示True),这里修改默认的状态为 False,只有用户邮箱激活后才改为True
user_profile.is_active = False
# 密码加密
user_profile.password = make_password(pass_word)
user_profile.save()
send_register_email(e_mail, 'register') # 由UserProfile/utils/mail_send.py 下定义发送邮件方法
messages.success(request, '邮件已发送,请前往邮件激活。如未收到邮件,请检查邮箱地址是否填写正确。')
return render(request, 'UserProfile/switch.html')
else:
return render(request, 'UserProfile/register.html')
7. 邮箱 激活邮件设计
/settings.py 中配置激活邮件发件邮箱:
# /setting.py
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = "smtp.qq.com"# 所用邮箱供应商提供的 host
EMAIL_PORT = 25# 端口号
EMAIL_HOST_USER = "**********@qq.com"# 邮箱地址
EMAIL_HOST_PASSWORD = "************"# 邮箱授权码,不是登陆密码
EMAIL_USE_TLS = False
EMAIL_USE_SSL = True# 我这里使用的是SSL连接
EMAIL_SSL_CERTFILE = None
EMAIL_SSL_KEYFILE = None
EMAIL_FROM = "*********@qq.com"# 邮箱地址
UserProfile/models.py 中建立激活邮件的数据库:
# UserProfile/models.py
from django.db import models
class EmailVerifyRecord(models.Model):
"""
验证码
"""
send_choices = (
('register', '注册'),
('forget', '找回密码')
)
code = models.CharField('验证码', max_length=20, unique=True)
email = models.EmailField('邮箱', max_length=50)
send_type = models.CharField(choices=send_choices, max_length=10)
send_time = models.DateTimeField(null=True)
class Meta:
verbose_name = '邮箱验证码'
verbose_name_plural = verbose_name
def __str__(self):
return self.code
# pycharm terminal 执行迁移数据库操作
python manage.py makemigration
python manage.py migrate
Apps/utils/mail_send.py 下定义发送邮件的方法
# UserProfile/utils/mail_send.py 下定义发送邮件的方法:
from random import Random
from django.core.mail import send_mail
from UserProfile.models import EmailVerifyRecord
from mysite.settings import EMAIL_FROM
from django.utils import timezone
def random_str(random_length=8):
code_str = ''
# 生成字符串的可选字符串
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(random_length):
code_str += chars[random.randint(0, length)]
return code_str
# 发送注册邮件
def send_register_email(email, send_type):
# 发送之前先保存到数据库,到时候查询链接是否存在
# 实例化一个EmailVerifyRecord对象
email_record = EmailVerifyRecord()
# 生成随机的code放入链接
code = random_str(16)
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.send_time = timezone.now()
email_record.save()
# 定义邮件内容:
email_title = ""
email_body = ""
if send_type == "register":
email_title = "Example - 注册激活链接"
email_body = "请点击下面的链接激活你的账号: http://127.0.0.1:8000/active/{0}".format(code)
# 使用Django内置函数完成邮件发送。四个参数:主题,邮件内容,发件人邮箱地址,收件人(是一个字符串列表)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功
if send_status:
pass
if send_type == "forget":
email_title = "Example - 找回密码"
email_body = "请点击下面的链接找回你的密码: http://127.0.0.1:8888/reset/{0}".format(code)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email])
# 如果发送成功
if send_status:
pass
点击激活邮件后的 激活业务逻辑设置:
# UserProfile/views/py
from django.views.generic import View
from django.shortcuts import render
from UserProfile.models import UserProfile, EmailVerifyRecord
from django.contrib import messages
class ActiveUserView(View):
"""
激活邮件
"""
@staticmethod
def get(request, active_code):
# 查询邮箱验证记录是否存在
all_record = EmailVerifyRecord.objects.filter(code=active_code)
if all_record:
for record in all_record:
# 获取到对应邮箱
email = record.email
# 查找到邮箱对应的 user
user = UserProfile.objects.get(email=email)
user.is_active = True
user.save()
messages.success(request, '激活成功,请登录!')
# 验证码不对的时候跳转到激活失败页面
else:
messages.error(request, '激活码错误,请重新注册')
return render(request, 'register.html')
# 激活成功 跳转到登录页面
return render(request, 'UserProfile/login.html')
/urls.py 中添加注册、登陆和邮箱验证的 url:
(Django urls.py 的操作不再展开赘述)
# /urls.py
from UserProfile.views import register, login, ActiveUserView
urlpatterns = [
path('register/', register, name='register'),
path('login/', login, name='login'),
path('active/<active_code>/', ActiveUserView.as_view(), name="user_active"),
]
# 建议多使用 include()函数,将url 分发到自路由。
额外内容
on_delete
Django 使用外键时(OneToOneField 和 ForeignKey) 要用到 on_delete
参数,不然就会报错。
将 on_delete 各参数的作用记录如下:
on_delete=None, # 删除关联表中的数据时,当前表与其关联的field的行为
on_delete=models.CASCADE, # 删除关联数据,与之关联也删除
on_delete=models.DO_NOTHING, # 删除关联数据,什么也不做
on_delete=models.PROTECT, # 删除关联数据,引发错误ProtectedError
# models.ForeignKey('关联表', on_delete=models.SET_NULL, blank=True, null=True)
on_delete=models.SET_NULL, # 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空,一对一同理)
# models.ForeignKey('关联表', on_delete=models.SET_DEFAULT, default='默认值')
on_delete=models.SET_DEFAULT, # 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值,一对一同理)
on_delete=models.SET, # 删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
多对多不需要 on_delete。想要了解更多 on_delete 的资料,请去官网查看。
django.db.models.ForeignKey
待补充:
- 忘记密码找回功能
- 设置注册及登录间隔
- 官方不建议在使用AbstractUser 继承后直接调用 apps.models.abstractuser 的模型,相反建议调用 settings.AUTH_USER_MODEL,但是我在调用后者的时候无法实现业务逻辑,后续考虑解决办法。
- 后续会在改善上述或其它问题后进行更新,欢迎大家随时后台与我讨论问题,会尽量一一答复。
- 作者水平有限,本文章仅介绍了最简略的Django注册登录系统,仅供参考,请勿用于商业环境。
文章评论