注意:win下面 pip install 安装python module失败后,可以去https://www.lfd.uci.edu/~gohlke/pythonlibs/网站才在相应的module,然后手动pip install 本地安装即可。
手动安装步骤如下: 需要切换到下载模块的目录下。
Models设计
users -- 用户管理
course -- 课程管理
organization -- 机构和教师管理
operation -- 用户操作管理
EmailVerifyRecord -- 邮箱验证码 和 PageBanner -- 轮播图 此两部分数据是独立的,再此处我们把它们放在users app 的 models.py中
由于系统自带的user表不能满足我们的需求,故我们可以修改系统自带的user表,按需增加字段。
auth_user为系统自带的user表,下图是此表中的各个字段。
我们可以继承此表的字段,然后不满足需求的,我们可以添加自己的字段。
继承方式是继承 django.contrib.auth.models 下面的AbstractUser类
用django开发,经常要处理用户上传的文件, 比如user模型里面如果又个人头像的字段 ImageField等等,而django在FielField字段(包括ImageField)的支持和扩展是做的很好的,首先一个问题,是上传的文件,django是放到哪里去了,(note: 文件流是不会放到数据库里面的,该字段在数据库中只存储路径),django提供了upload_to属性
以下介绍upload_to的具体使用方法
1.最直接的方式,硬编码路径
# MyProject.settings.py 里面设置MEDIA_ROOT and MEDIA_URL
MEDIA_ROOT = os.path.join(BASE_DIR, 'upload/')
MEDIA_URL = '/upload/' #这个是在浏览器上访问该上传文件的url的前缀
# models.py
class User(models.Model):
avatar = ImageField(upload_to = 'avatar/')
#实际的路径就是 MEDIA_ROOT/avatar/filename
#所以可以用uoload_to来指定文件存放的前缀路径
2.使用strftime()
如果觉得以上方式太僵硬,万一文件重名了,那就会有各种问题了,为了避免重名,django在upload_to上内置了strftime()函数
# models.py
class User(models.Model):
avatar = ImageField(upload_to = 'avatar/%Y/%m/%d/')
这样子的方式,%Y、%m、%d分别表示年、月、日
3.更加灵活的方式
当然,如果觉得只是避免文件路径重名,还是不能满足你,其实,django还允许你重写一个upload_to函数,重定义上传文件的路径前缀
# models.py
#让上传的文件路径动态地与user的名字有关
def upload_to(instance, fielname):
return '/'.join([MEDIA_ROOT, instance.user_name, filename])
class User(models.Model):
avatar = ImageField(upload_to = upload_to)
user_name = CharField(max_length = 250)
继承auth.user下面的user表
在users models.py中 新增UserProfile类
并且需要在setting.py配置文件中添加如下声明信息,否则报错:
AUTH_USER_MODEL = "users.UserProfile" # 重载setting的方法, 用自定义的UserProfile 覆盖 默认的user表
models.py
class UserProfile(AbstractUser):
nick_name = models.CharField(max_length=50, verbose_name=u"昵称", default=u"")
birthday = models.DateField(verbose_name=u"生日", null=True, blank=True) # 允许为空
gender = models.CharField(choices=(("male", u"男"), ("female", u"女")), default='female', max_length=6)
address = models.CharField(max_length=100, default=u"")
mobile = models.CharField(max_length=11, null=True, blank=True)
image = models.ImageField(upload_to="image/%Y/%m", default=u"image/default.png", max_length=100)
class Meta:
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self): # 重载此方法
return self.username
重新makemigrations 和 migrate 即可。 若报错 可将数据库中的表文件全部删除,再执行迁移 和 生成表即可。
至此 用UserProfile 替换user表完成。
解决循环import的方法 -- 分层设计
循环引用会使各个models.py之间形成死循环,而出现死锁。
分成方法 -- 使用一个高于users、courses、organization 应用的 operation app。记录用户相关的操作。即把 users 和 courses 之间的联系,users 和 organization 之间的联系,都放在更高一层的operation app中。
上一层的 operation app 可以 import 下一层的各个app,这样的话就可以防止循环import
users app的model中有三个class(三张表格)
# 由于邮箱验证码 和 轮播图 是两个和其他组件都相对独立的功能,这里我们把它俩放在users app 中
class EmailVerifyRecord(models.Model): # 邮箱验证码
code = models.CharField(max_length=20, verbose_name=u"验证码")
email = models.EmailField(max_length=50, verbose_name=u"邮箱")
send_type = models.CharField(choices=(('register', u"注册"), ('forget', u"找回密码")), max_length=10)
send_time = models.DateTimeField(default=datetime.now)
class Meta:
verbose_name = u"邮箱验证码"
verbose_name_plural = verbose_name
class Banner(models.Model): # 轮播图
title = models.CharField(max_length=100, verbose_name=u"标题") # 图片名字
image = models.ImageField(upload_to="banner/%Y/%m", verbose_name="轮播图", max_length=100)
url = models.URLField(max_length=200, verbose_name=u"访问地址") # 页面跳转
index = models.IntegerField(default=100, verbose_name=u"顺序") # 滚动顺序
send_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")
class Meta:
verbose_name = u"轮播图"
verbose_name_plural = verbose_name
course app 中的class(数据表)
from django.db import models
from datetime import datetime
# Create your models here.
class Course(models.Model): # 课程信息
name = models.CharField(max_length=50, verbose_name=u"课程名")
desc = models.CharField(max_length=300, verbose_name=u"课程描述")
detail = models.TextField(verbose_name=u"课程详情") # 不限制输入长度
degree = models.CharField(choices=(('cj', "初级"), ('zj', "中级"), ('gj', "高级")), max_length=5)
learn_times = models.IntegerField(default=0, verbose_name=u"学习时长(分钟数)")
students = models.IntegerField(default=0, verbose_name=u"学习人数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏人数")
image = models.ImageField(upload_to="courses/%Y/%m", verbose_name="封面图", max_length=100)
click_nums = models.IntegerField(default=0, verbose_name="点击数")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "课程"
verbose_name_plural = verbose_name
class Lesson(models.Model): # 章节信息
course = models.ForeignKey(Course, verbose_name="课程") # 每门课程有多个章节
name = models.CharField(max_length=100, verbose_name="章节名")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "章节信息"
verbose_name_plural = verbose_name
class Video(models.Model): # 每章视频信息
lesson = models.ForeignKey(Lesson, verbose_name="章节") # 每个章节有多个视频
name = models.CharField(max_length=100, verbose_name="视频名")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "章节内视频信息"
verbose_name_plural = verbose_name
class CourseResource(models.Model): # 课程资源
course = models.ForeignKey(Course, verbose_name="课程") # 每门课程有多个课程资源
name = models.CharField(max_length=100, verbose_name="资源名称")
# 后台管理页面会自动生成上传文件的按钮
download = models.FileField(upload_to='course/resource/%Y/%m', verbose_name="资源文件", max_length=100)
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "课程资源"
verbose_name_plural = verbose_name
organization app 中的class(数据表)
from django.db import models
from datetime import datetime
# Create your models here.
class CityDict(models.Model): # 城市信息
name = models.CharField(max_length=20, verbose_name="城市")
desc = models.CharField(max_length=200, verbose_name="描述")
add_time = models.DateTimeField(default=datetime.now)
class Meta:
verbose_name = "城市"
verbose_name_plural = verbose_name
class CourseOrg(models.Model):
name = models.CharField(max_length=50, verbose_name="机构名称")
desc = models.TextField(verbose_name="机构描述")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏数")
image = models.ImageField(upload_to="org/%Y/%m", verbose_name="封面图", max_length=100)
address = models.CharField(max_length=150, verbose_name="机构地址")
city = models.ForeignKey(CityDict, verbose_name="所在城市")
add_time = models.DateTimeField(default=datetime.now)
class Meta:
verbose_name = "课程机构"
verbose_name_plural = verbose_name
class Teacher(models.Model):
org = models.ForeignKey(CourseOrg, verbose_name="所属机构")
name = models.CharField(max_length=50, verbose_name="教师名")
work_years = models.IntegerField(default=0, verbose_name="工作年限")
work_company = models.CharField(max_length=50, verbose_name="就职公司")
work_position = models.CharField(max_length=50, verbose_name="公司职位")
points = models.CharField(max_length=50, verbose_name="教学特点")
click_nums = models.IntegerField(default=0, verbose_name="点击数")
fav_nums = models.IntegerField(default=0, verbose_name="收藏数")
add_time = models.DateTimeField(default=datetime.now)
class Meta:
verbose_name = "教师"
verbose_name_plural = verbose_name
operation app 中的class(数据表)
from django.db import models
from datetime import datetime
from users.models import UserProfile
from courses.models import Course
# Create your models here.
class UserAsk(models.Model):
name = models.CharField(max_length=20, verbose_name="姓名")
mobile = models.CharField(max_length=11, verbose_name="手机")
course_name = models.CharField(max_length=50, verbose_name="课程名")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "用户咨询"
verbose_name_plural = verbose_name
class CourseComment(models.Model): # 课程评论
user = models.ForeignKey(UserProfile, verbose_name="用户", on_delete=models.CASCADE)
course = models.ForeignKey(Course, verbose_name="课程", on_delete=models.CASCADE)
comment = models.CharField(max_length=200, verbose_name="评论")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "课程评论"
verbose_name_plural = verbose_name
class UserFavorite(models.Model):
user = models.ForeignKey(UserProfile, verbose_name="用户", on_delete=models.CASCADE)
fav_id = models.IntegerField(default=0, verbose_name="数据id") # 取值1/2/3 见下一行
fav_type = models.IntegerField(choices=((1, "课程"), (2, "课程机构"), (3, "讲师")), default=1, verbose_name="收藏类型")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "用户收藏"
verbose_name_plural = verbose_name
class UserMessage(models.Model):
user = models.IntegerField(default=0, verbose_name="接收用户") # 0 指定为发给所有用户的消息,非0的话则取值为用户id
message = models.CharField(max_length=500, verbose_name="消息内容")
has_read = models.BooleanField(default=False, verbose_name="是否已读") # 会统计未读消息条数
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "用户消息"
verbose_name_plural = verbose_name
class UserCourse(models.Model):
user = models.ForeignKey(UserProfile, verbose_name="用户", on_delete=models.CASCADE)
course = models.ForeignKey(Course, verbose_name="课程", on_delete=models.CASCADE)
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
class Meta:
verbose_name = "用户课程"
verbose_name_plural = verbose_name
注册apps
然后 执行 makemigration 生成迁移文件,再然后 migrate 根据迁移文件生成对应的数据表
接下来 创建apps的python package,并在setting.py中设置搜索路径,从而避免需要引入apps文件夹(apps搜索目录的配置)
Django后台管理系统
设置setting.py文件中的 三个参数
然后把数据表在各自app文件中的admin.py中进行注册
xadmin的安装与使用
https://github.com/sshwsfc/xadmin/tree/django2 下载zip文件
然后pip install 所下载的zip文件 即可。
安装完xadmin之后 需要在installed_apps中进行注册,除了注册xadmin之外,还要注册其依赖的module:crispy_forms(这里是下划线,下面图片里有错误。注意!)
修改urls的配置
启动服务器后发现报错如下
原因是未生成xadmin的数据表。 需要同步 xadmin的迁移文件 和 生成表
python manage.py makemigrations
Migrations for 'operation':
apps\operation\migrations\0002_auto_20181124_2228.py
- Alter field fav_type on userfavorite
(first_django_pj) G:\XiaoXiaoHan\MxOnline>python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, courses, operation, organization, sessions, users, xadmin
Running migrations:
Applying operation.0002_auto_20181124_2228... OK
Applying xadmin.0001_initial... OK
Applying xadmin.0002_log... OK
Applying xadmin.0003_auto_20160715_0100... OK
(first_django_pj) G:\XiaoXiaoHan\MxOnline>
验证登录账号 和 密码 是否正确
authenticate(账号, 密码) 若验证成功返回相关对象,不成功返回None。 (只验证不登录)
login(request, 上面返回的对象) 此为登录
后台自定义AUTH方法:使得不单单只能用账号密码登录,也可以诸如使用邮箱登录。
注册各个app中的model类:在app文件夹中新建adminx.py文件。如在 users app中 adminx.py 文件内容如下
import xadmin
from .models import EmailVerifyRecord, Banner
class EmailVerifyRecordAdmin(object):
list_display = ['code', 'email', 'send_type', 'send_time'] # 管理页面显示列
search_fields = ['code', 'email', 'send_type'] # 管理页面搜索列
list_filter = ['code', 'email', 'send_type', 'send_time'] # 筛选列
class BannerAdmin(object):
list_display = ['title', 'image', 'url', 'index', 'send_time'] # 管理页面显示列
search_fields = ['title', 'image', 'url', 'index'] # 管理页面搜索列
list_filter = ['title', 'image', 'url', 'index', 'send_time'] # 筛选列
xadmin.site.register(EmailVerifyRecord, EmailVerifyRecordAdmin)
xadmin.site.register(Banner, BannerAdmin)
更改主题样式 以及 给 各个app的表格设置可折叠样式
在uesrs app下添加两个类:
from xadmin import views
class BaseSetting(object):
enable_themes = True # 主题功能,可以选择不同的主题
use_bootswatch = True # 样式
xadmin.site.register(views.BaseAdminView, BaseSetting) # 注册基础配置类
class GlobalSetting(object):
site_title = '面试系统'
site_footer = '面试系统 All rights reserved'
menu_style = 'accordion' # 折叠每个app下面的表格
xadmin.site.register(views.CommAdminView, GlobalSetting)
app显示 自定义名字。两步: 此处以courses课程app为例
step 1:在courses文件夹下的apps.py下添加verbose_name属性
from django.apps import AppConfig
class CoursesConfig(AppConfig):
name = 'courses'
verbose_name = '课程管理'
step 2:在courses文件夹下的__init__.py文件中 添加 default_app_config属性
default_app_config = 'courses.apps.CoursesConfig'
保存之后 刷新页面。效果如下:
用户登录、注册以及找回密码
- 登录
设置静态模板页面的方法
from django.urls import path
from django.views.generic import TemplateView
import xadmin
urlpatterns = [
path('xadmin/', xadmin.site.urls), # 选用xadmin
# 静态页面模板
path('', TemplateView.as_view(template_name='index.html'), name='index')
]
操作app 下面的views.py文件
from django.shortcuts import render
from django.contrib.auth import authenticate # authenticate 鉴定
from django.contrib.auth import login # 登录方法
# Create your views here.
def user_login(request):
if request.method == 'POST': # 登录页面之内 输入用户名密码登录是post方式提交
user_name = request.POST.get('username', '')
pass_word = request.POST.get('password', '')
# 向数据库发起验证 用户名 密码 是否正确,但是即使验证成功也不会自动登录
user = authenticate(username=user_name, password=pass_word) # 验证失败返回 None
if user is not None: # 若验证成功,则进行登录,并返回登陆成功的主页面
login(request, user)
return render(request, 'index.html', {})
else:
# print('验证失败,请重新登录!')
return render(request, 'login.html', {}) # 验证失败重新返回登录页面
elif request.method == 'GET': # 主页点击登录按钮 是get方式提交
# print('get 方式')
return render(request, 'login.html', {})
设置可以通过邮箱名 和 密码登录,需要在setting.py文件中 添加一行:
AUTHENTICATION_BACKENDS = ( # 添加自定义的类
'users.views.CustomBackend',
)
然后在views.py中 编写此类
from django.shortcuts import render
from django.contrib.auth import authenticate # authenticate 鉴定
from django.contrib.auth import login # 登录方法
from django.contrib.auth import backends
from django.db.models import Q # 取并集
from .models import UserProfile
# Create your views here.
class CustomBackend(backends.ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs): # 重写此方法,不用改变下面方法的代码
try:
user = UserProfile.objects.get(Q(username=username) | Q(email=username)) # 取并集
if user.check_password(password): # 验证密码的方法
return user
except Exception as e:
return None
此时就可以用邮箱和密码登录了。
重写view的方法,采用Django基于类的方法书写各个功能模块的逻辑(比如login)
之前的(登录方式)的配制方法是基于函数的,下面要实现基于类的方法。
from django.views.generic.base import View # 基类
重写get 和 post 方法
from django.shortcuts import render
from django.contrib.auth import authenticate # authenticate 鉴定
from django.contrib.auth import login # 登录方法
from django.contrib.auth import backends
from django.db.models import Q # 取并集
from django.views.generic.base import View # 基类,实现基于类的方法 的类都要继承此基类
from .models import UserProfile
# Create your views here.
class LoginView(View):
def get(self, request):
return render(request, 'login.html', {})
def post(self, request):
user_name = request.POST.get('username', '')
pass_word = request.POST.get('password', '')
# 向数据库发起验证用户名密码是否正确,但是即使验证成功也不会自动登录
user = authenticate(username=user_name, password=pass_word) # 验证失败返回 None
if user is not None: # 若验证成功,则进行登录,并返回登陆成功的主页面
login(request, user)
return render(request, 'index.html', {})
else:
print('验证失败,请重新登录!')
return render(request, 'login.html', {'msg': '用户名或密码错误!'}) # 验证失败重新返回登录页面
修改url
re_path('^login/$', views.LoginView.as_view(), name='login'), # 此处是直接调用as_view方法,而不是传函数名
然后把views.py中的user_login方法以及对应的url注释掉。发现功能和未注释之前是相同的。
通过form 对用户名和密码做验证
目的是减少查询数据库的负担。比如用户名没填或者密码长度太短,这样直接提示问题所在,而不需要去查询数据库之后再报错
首先在app的目录下新建forms.py文件
from django import forms
class LoginForm(forms.Form): # 继承此类
username = forms.CharField(required=True) # 说明此字段是必填字段
password = forms.CharField(required=True, min_length=6) # 密码长度至少为6
然后再views.py中 类方法调用上面的LoginForm类
from django.shortcuts import render
from django.contrib.auth import authenticate # authenticate 鉴定
from django.contrib.auth import login # 登录方法
from django.contrib.auth import backends
from django.db.models import Q # 取并集
from django.views.generic.base import View # 基类,实现基于类的方法 的类都要继承此基类
from .models import UserProfile
from .forms import LoginForm # 引入forms中的类
# Create your views here.
class LoginView(View): # 用类的方式实现原先view中函数的功能
def get(self, request):
return render(request, 'login.html', {})
def post(self, request):
login_form = LoginForm(data=request.POST) # 实例对象,注意:需要添加此参数,否则is_valid()一直是false
print(login_form.is_valid())
if login_form.is_valid(): # 如果此实例对象有效,即满足LoginForm类中的所有条件,再往下执行
user_name = request.POST.get('username', '')
pass_word = request.POST.get('password', '')
# 向数据库发起验证用户名密码是否正确,但是即使验证成功也不会自动登录
user = authenticate(username=user_name, password=pass_word) # 验证失败返回 None
if user is not None: # 若验证成功,则进行登录,并返回登陆成功的主页面
login(request, user)
return render(request, 'index.html', {})
else:
print('name or password')
return render(request, 'login.html', {'msg': '用户名或密码错误!', 'login_form': login_form}) # 验证失败重新返回登录页面
else:
print('form error')
return render(request, 'login.html', {'login_form': login_form}) # 验证失败重新返回登录页面
就是 用户名为必填项 密码长度不能少于6位
效果如下:
当用户名非空,且密码长度不少于6位时,才获取实际输入的用户名和密码去数据库中查询
session 和 cookie 自动登录机制
cookie是浏览器支持的一种本地存储方式
为了避免cookie中存储用户名和密码等不安全因素,引出了session机制,session是服务器生成的,存储在服务器端的。session随服务器返回的信息一起返回给浏览器,浏览器会把session存储在cookie中。
在下一次请求浏览器时,会把cookie中的session信息一并带给服务器,服务器通过查询session的信息来判断此用户是此前哪一个用户。然后浏览器就可以为此用户做标记。
Django利用cookie和session的机制,完成了自动登录的功能。 -- 注意:session是有时间限制的,可以按需自行修改。
用户注册
验证码:
step1:安装captcha库
pip install django-simple-captcha
step2:setting.py中注册app
step3: 通过python manage.py migrate 生成数据表 -- 存储图片路径地址的数据表
step4:url中配置
from django.urls import path, re_path, include
urlpatterns = [
...
re_path('^captcha/', include('captcha.urls')), # 注意 这里正则表达式没有结尾的$符号,否则报错
]
step5: forms.py中:
from captcha.fields import CaptchaField
class RegisterForm(forms.Form):
email = forms.EmailField(required=True)
password = forms.CharField(required=True, min_length=6)
captcha = CaptchaField(error_messages={'invalid':'验证码错误'})
# 生产验证码字段,并自定义错误信息,键是invilid
step6: views.py中:
class RegisterView(View):
def get(self,request):
register_form = RegisterForm() #生产实例
return render(request,'register.html',{'register_form':register_form}) #传入模板
step7: register.html中:
<div class="form-group marb8 captcha1 ">
<label>验 证 码</label>
{{ register_form.captcha }} #模板中应用
</div>
发送邮件业务逻辑
写一个发送邮件的基础函数
step1:在apps下面新建一个package文件夹,命名为utils,在此文件夹下新建send_email.py文件。(单放在package里面也方便其他阶段的调用。)内部代码如下:
发送确认链接邮件之前先把此链接的相关信息保存进数据库,因为用户点击确认链接时,需要在数据库中判断此链接是否是有效链接(即是否在数据库中存在。)
# -*- coding: utf-8 -*-
__date__ = '2018/11/27 0:59'
from random import Random
from django.core.mail import send_mail # 自动发邮件的函数
from users.models import EmailVerifyRecord
from MxOnline.settings import EMAIL_FROM
def random_str(random_length=8): # 默认随机字符串的长度为8
ss = ''
chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
length = len(chars) - 1
random = Random()
for i in range(random_length):
ss += chars[random.randint(0, length)]
return ss
def send_register_email(email, send_type="register"):
email_record = EmailVerifyRecord() # 创建对象实例
code = random_str(16) # 这里生成16位的随机字符串
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save() # 存入数据库
# 下面是发送邮件的代码
email_title = '' # 邮件标题
email_body = '' # 邮件内容
if send_type == 'register':
email_title = '在线面试系统激活链接'
email_body = '请点击下面的链接激活你的账号:https://127.0.0.1:8000/active/{0}'.format(code)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) # 返回Boolean类型
if send_status:
print('邮件发送成功')
step2:另外需要在setting.py中添加如下配置代码,email_send.py文件send_register方法中的send_email方法会找到对应参数。
EMAIL_HOST = 'smtp.fudan.edu.cn' # 你邮箱的smtp域名地址
EMAIL_PORT = 25 # 默认端口号
EMAIL_HOST_USER = 'xxx@fudan.edu.cn' # 你邮箱名字
EMAIL_HOST_PASSWORD = 'xxx' # 邮箱密码
EMAIL_FROM = 'xxx@fudan.edu.cn' # 发件人,一般是上边邮箱名字
step3:views.py中的 RegisterView类方法调用此函数
from utils.email_send import send_register_email # 发送邮件函数
# Create your views here.
class RegisterView(View):
def get(self, request):
register_form = RegisterForm()
return render(request, 'register.html', {'register_form': register_form})
def post(self, request):
register_form = RegisterForm(data=request.POST) # 实例对象,注意:需要添加此参数,否则is_valid()一直是false
if register_form.is_valid():
user_name = request.POST.get('email', '')
pass_word = request.POST.get('password', '')
user_profile = UserProfile() # 定义一个实例对象
user_profile.username = user_name
user_profile.email = user_name
user_profile.is_active = False # 用户注册时默认注册账号未激活,需要在邮箱中点击激活链接,才算账号注册成功。
user_profile.password = make_password(pass_word) # 对明文密码进行加密
user_profile.save() # 保存到数据库中
send_register_email(user_name, 'register') # 调用发送邮件的方法
print('succeed send email...')
return render(request, 'login.html', {}) # 注册成功返回登录页面
else:
return render(request, 'register.html', {'register_form': register_form}) # 注册失败 仍返回注册页面
这里用的是sina的邮箱,需要开启smtp服务。效果如下:
激活账户 -- 点击邮箱中激活链接
step1:默认未激活,将is_active字段设置为Flase
class RegisterView(View):
def get(self, request):
...
def post(self, request):
...
user_profile.is_active = False # 用户注册时默认注册账号未激活,需要在邮箱中点击激活链接,才算账号注册成功。
...
step2:在views.py中 新建ActiveUserView类 并定义get方法,get方法中除了request变量,还有另外的变量。
from django.views.generic.base import View # 基类,实现基于类的方法 的类都要继承此基类
from .models import UserProfile, EmailVerifyRecord
class ActiveUserView(View): # 激活链接邮件激活账户
def get(self, request, active_code): # active_code 即为生成的随机字符串
all_records = EmailVerifyRecord.objects.filter(code=active_code) # 过滤取所有code字段等于active_code的记录
if all_records: # 若all_records非空
for record in all_records:
email = record.email
user = UserProfile.objects.get(email=email) # 获取一条记录
user.is_active = True # 修改激活字段为 激活状态,然后保存
user.save()
return render(request, 'login.html', {}) # 激活之后跳转到登录页面
step3: url配置 -- 有变量
from django.urls import path, re_path, include
from users import views
import xadmin
urlpatterns = [
...
re_path('^active/(?P<active_code>.*)/$', views.ActiveUserView.as_view(), name='user_active'), # 激活链接
]
step4:修改登录视图类 -- LoginView。只有当user_profile记录中is_active字段为True时 才允许登录。
class LoginView(View): # 用类的方式实现原先view中函数的功能
def get(self, request):
return render(request, 'login.html', {})
def post(self, request):
login_form = LoginForm(data=request.POST) # 实例对象,注意:需要添加此参数,否则is_valid()一直是false
# print(login_form.is_valid())
if login_form.is_valid(): # 如果此实例对象有效,即满足LoginForm类中的所有条件,再往下执行
user_name = request.POST.get('username', '')
pass_word = request.POST.get('password', '')
# 向数据库发起验证用户名密码是否正确,但是即使验证成功也不会自动登录
user = authenticate(username=user_name, password=pass_word) # 验证失败返回 None
if user is not None: # 若验证成功,则继续判断该用户是否已经激活
if user.is_active: # 若已经激活,则进行登录,并返回登陆成功的主页面
login(request, user)
return render(request, 'index.html', {})
else: # 否为返回 用户未激活
return render(request, 'login.html', {'msg': '用户未激活!', 'login_form': login_form})
else:
# 验证失败重新返回登录页面
return render(request, 'login.html', {'msg': '用户名或密码错误!', 'login_form': login_form})
else:
print('form error')
return render(request, 'login.html', {'login_form': login_form}) # 验证失败重新返回登录页面
效果:
数据库users_userprofile表中,有一个记录未激活,如下:
若使用此账号登录:
下面打开激活链接,点开之后会自动跳转到登录界面,在跳转之前已经完成激活操作,即修改此记录的is_active字段为True
然后再次输入刚刚的邮箱和密码,登录成功!:)
邮箱注册时,若该邮箱已经注册过此系统,则应该返回该用户已存在。 实现如下:
class RegisterView(View):
def get(self, request):
register_form = RegisterForm()
return render(request, 'register.html', {'register_form': register_form})
def post(self, request):
register_form = RegisterForm(data=request.POST) # 实例对象,注意:需要添加此参数,否则is_valid()一直是false
if register_form.is_valid():
user_name = request.POST.get('email', '')
if UserProfile.objects.filter(email=user_name): # 如果不返回空,则说明此邮箱已经注册
return render(request, 'register.html', {'msg': '此邮箱已注册!', 'register_form': register_form})
pass_word = request.POST.get('password', '')
user_profile = UserProfile() # 定义一个实例对象
user_profile.username = user_name
user_profile.email = user_name
user_profile.is_active = False # 用户注册时默认注册账号未激活,需要在邮箱中点击激活链接,才算账号注册成功。
user_profile.password = make_password(pass_word) # 对明文密码进行加密
user_profile.save() # 保存到数据库中
send_register_email(user_name, 'register') # 调用发送邮件的方法
print('succeed send email...')
return render(request, 'login.html', {}) # 注册成功返回登录页面
else:
return render(request, 'register.html', {'register_form': register_form}) # 注册失败 仍返回注册页面
效果
链接失效问题
如果用户点击的激活链接有误 或者 已经过了有效期,则均可以判为链接失效。 新建一个html文件,验证失败时跳转即可。
views.py中 逻辑:
class ActiveUserView(View): # 激活链接邮件激活账户
def get(self, request, active_code): # active_code 即为生成的随机字符串
all_records = EmailVerifyRecord.objects.filter(code=active_code) # 过滤取所有code字段等于active_code的记录
if all_records: # 若all_records非空
for record in all_records:
email = record.email
user = UserProfile.objects.get(email=email) # 获取一条记录
user.is_active = True # 修改激活字段为 激活状态,然后保存
user.save()
else:
return render(request, 'active_fail.html', {}) # 链接失效
return render(request, 'login.html', {}) # 激活之后跳转到登录页面
找回密码
step1:点击登录页面的忘记密码,然后跳转到 密码找回页面
login.html -- 配置跳转路由
<div class="auto-box marb38">
<a class="fr" href="{% url 'forget_pwd' %}">忘记密码?</a>
</div>
urls.py文件添加对应路由
from django.urls import path, re_path, include
from django.views.generic import TemplateView # 使用静态模板 不需要在app下面的views.py文件中配置render
from users import views
import xadmin
urlpatterns = [
...
re_path('^forget/$', views.ForgetPwdView.as_view(), name='forget_pwd'),
]
views.py文件中创建 视图类 --- ForgetPwdView
class ForgetPwdView(View):
def get(self, request):
forget_form = ForgetForm()
return render(request, 'forgetpwd.html', {'forget_form': forget_form})
def post(self, request):
forget_form = ForgetForm(data=request.POST)
if forget_form.is_valid():
email = request.POST.get('email', '')
# 发送激活链接 邮件
send_register_email(email, 'forget')
return render(request, 'send_success.html') # 邮件发送成功
else: # 表单验证失败,则还是返回忘记密码页面
return render(request, 'forgetpwd.html', {'forget_form': forget_form})
视图类调用了ForgetForm类 -- 在forms.py文件中
class ForgetForm(forms.Form):
email = forms.EmailField(required=True)
captcha = CaptchaField(error_messages={'invalid': '验证码错误'})
# 生产验证码字段,并自定义错误信息,键是invilid
step2:在密码找回页面中 输入邮箱 和 验证码,点击提交,会发送密码修改链接至对应邮箱,并跳转到提示邮件发送成功页面
补全发送邮件函数 ---- utils文件夹下email_send.py中的 send_register_email函数
def send_register_email(email, send_type="register"):
email_record = EmailVerifyRecord() # 创建对象实例
code = random_str(16) # 这里生成16位的随机字符串
email_record.code = code
email_record.email = email
email_record.send_type = send_type
email_record.save() # 存入数据库
# 下面是发送邮件的代码
email_title = '' # 邮件标题
email_body = '' # 邮件内容
if send_type == 'register':
email_title = '在线面试系统激活链接'
email_body = '请点击下面的链接激活你的账号:https://127.0.0.1:8000/active/{0}'.format(code)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) # 返回Boolean类型
if send_status:
print('邮件发送成功')
elif send_type == 'forget':
email_title = '在线面试系统密码重置'
email_body = '请点击下面的链接重置密码:https://127.0.0.1:8000/reset/{0}'.format(code)
send_status = send_mail(email_title, email_body, EMAIL_FROM, [email]) # 返回Boolean类型
if send_status:
print('邮件发送成功')
step3:
配置能打开邮箱中链接的路由urls.py
from django.urls import path, re_path, include
from django.views.generic import TemplateView # 使用静态模板 不需要在app下面的views.py文件中配置render
from users import views
import xadmin
urlpatterns = [
...
re_path('^reset/(?P<active_code>.*)/$', views.ResetView.as_view(), name='reset_pwd'), # reset
]
views.py文件中创建 视图类 --- ResetView。根据active_code随机字符串 去数据库里查找此字符串,所能找到记下相应邮箱,并返回一个重置密码的页面。
class ResetView(View): # 激活链接邮件激活账户
def get(self, request, active_code): # active_code 即为生成的随机字符串
all_records = EmailVerifyRecord.objects.filter(code=active_code) # 过滤取所有code字段等于active_code的记录
if all_records: # 若all_records非空
for record in all_records:
email = record.email
return render(request, 'password_reset.html', {'email': email})
else:
return render(request, 'active_fail.html', {}) # 链接失效
return render(request, 'login.html', {}) # 激活之后跳转到登录页面
打开链接显示的页面如下
step4:检验两次输入的密码是否一致。若一致则会把更改更新到数据库中,并跳转到登录页面。若不一致,则提示两次输入的面不一致。
由于ResetView类需要额外的active_code参数,所以不能共用一个类(提交表单用的是post方式,页面跳转用的是get方式)。
故在views.py中重新定义一个视图类 --- ModifyPwdView。 由于用到了表单,故在forms.py中定义一个验证类ModifyPwdForm
class ModifyPwdView(View):
def post(self, request):
modify_form = ModifyPwdForm(data=request.POST) # 实例化
if modify_form.is_valid():
pwd1 = request.POST.get('password1', '')
pwd2 = request.POST.get('password2', '')
email = request.POST.get('email', '')
if pwd1 != pwd2:
return render(request, 'password_reset.html', {'email': email, 'msg': '密码不一致'})
user = UserProfile.objects.get(email=email)
user.password = make_password(pwd1) # 明文密码先加密,再存入数据库中
user.save()
# 密码修改成功之后返回到登录页面
return render(request, 'login.html', {})
else:
email = request.POST.get('email', '')
return render(request, 'password_reset.html', {'email': email, 'modify_form': modify_form})
# 定义表单验证类
class ModifyPwdForm(forms.Form): # 修改密码
password1 = forms.CharField(required=True, min_length=6)
password2 = forms.CharField(required=True, min_length=6)
配置url
re_path('^modify_pwd/$', views.ModifyPwdView.as_view(), name='modify_pwd'),
password_reset.html 中form表单的action 修改成上面的url
<form id="reset_password_form" action="{% url 'modify_pwd' %}" method="post">
<ul>
<li>
<span class="">新 密 码 :</span>
<input type="password" name="password1" id="pwd" placeholder="6-20位非中文字符">
<i></i>
</li>
<input type="hidden" name="email" value="{{ email }}">
<li>
<span class="">确定密码:</span>
<input type="password" name="password2" id="repwd" placeholder="6-20位非中文字符">
<i></i>
</li>
<div class="error btns" id="jsForgetTips">{{ msg }}</div>
<li class="button">
<input type="submit" value="提交">
</li>
</ul>
{% csrf_token %}
</form>
效果:密码已更新
注意:一般激活链接或者修改密码的链接只能使用一次,可以在EmailVerifyRecord类中添加一个字段 链接是否已打开,默认为False。当激活用户名 is_active=True保存到数据库之后 或者 修改密码完成 新密码保存到数据库之后 设置此字段为True。只有此字段为False时才允许打开此链接,否则报链接失效。
此外,还可以在EmailVerifyRecord中设置一个有效期字段,表示链接是有有效期限制的。