100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > Django 慕课前后端实战 -- xadmin后台管理系统 登录模块 注册模块 邮箱激活链接 找回密码

Django 慕课前后端实战 -- xadmin后台管理系统 登录模块 注册模块 邮箱激活链接 找回密码

时间:2020-01-19 08:29:13

相关推荐

Django 慕课前后端实战 -- xadmin后台管理系统 登录模块 注册模块 邮箱激活链接 找回密码

注意: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之对FileField字段的upload_to的设定

用django开发,经常要处理用户上传的文件, 比如user模型里面如果又个人头像的字段 ImageField等等,而django在FielField字段(包括ImageField)的支持和扩展是做的很好的,首先一个问题,是上传的文件,django是放到哪里去了,(note: 文件流是不会放到数据库里面的,该字段在数据库中只存储路径),django提供了upload_to属性

以下介绍upload_to的具体使用方法

1.最直接的方式,硬编码路径

# MyProject.settings.py 里面设置MEDIA_ROOT and MEDIA_URLMEDIA_ROOT = os.path.join(BASE_DIR, 'upload/')MEDIA_URL = '/upload/' #这个是在浏览器上访问该上传文件的url的前缀# models.pyclass User(models.Model):avatar = ImageField(upload_to = 'avatar/')#实际的路径就是 MEDIA_ROOT/avatar/filename#所以可以用uoload_to来指定文件存放的前缀路径

2.使用strftime()

如果觉得以上方式太僵硬,万一文件重名了,那就会有各种问题了,为了避免重名,django在upload_to上内置了strftime()函数

# models.pyclass 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_namedef __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_nameclass 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 modelsfrom 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_nameclass 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_nameclass 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_nameclass 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 modelsfrom 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_nameclass 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_nameclass 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 modelsfrom datetime import datetimefrom users.models import UserProfilefrom 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_nameclass 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_nameclass 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_nameclass UserMessage(models.Model):user = models.IntegerField(default=0, verbose_name="接收用户") # 0 指定为发给所有用户的消息,非0的话则取值为用户idmessage = 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_nameclass 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的安装与使用

/sshwsfc/xadmin/tree/django2下载zip文件

然后pip install 所下载的zip文件 即可。

安装完xadmin之后 需要在installed_apps中进行注册,除了注册xadmin之外,还要注册其依赖的module:crispy_forms(这里是下划线,下面图片里有错误。注意!)

修改urls的配置

启动服务器后发现报错如下

原因是未生成xadmin的数据表。 需要同步 xadmin的迁移文件 和 生成表

python manage.py makemigrationsMigrations for 'operation':apps\operation\migrations\0002_auto_1124_2228.py- Alter field fav_type on userfavorite(first_django_pj) G:\XiaoXiaoHan\MxOnline>python manage.py migrateOperations to perform:Apply all migrations: admin, auth, contenttypes, courses, operation, organization, sessions, users, xadminRunning migrations:Applying operation.0002_auto_1124_2228... OKApplying xadmin.0001_initial... OKApplying xadmin.0002_log... OKApplying xadmin.0003_auto_0715_0100... OK(first_django_pj) G:\XiaoXiaoHan\MxOnline>

验证登录账号 和 密码 是否正确

authenticate(账号, 密码) 若验证成功返回相关对象,不成功返回None。 (只验证不登录)

login(request, 上面返回的对象) 此为登录

后台自定义AUTH方法:使得不单单只能用账号密码登录,也可以诸如使用邮箱登录。

注册各个app中的model类:在app文件夹中新建adminx.py文件。如在 users app中 adminx.py 文件内容如下

import xadminfrom .models import EmailVerifyRecord, Bannerclass 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 viewsclass 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(mAdminView, GlobalSetting)

app显示 自定义名字。两步: 此处以courses课程app为例

step 1:在courses文件夹下的apps.py下添加verbose_name属性

from django.apps import AppConfigclass CoursesConfig(AppConfig):name = 'courses'verbose_name = '课程管理'

step 2:在courses文件夹下的__init__.py文件中 添加 default_app_config属性

default_app_config = 'courses.apps.CoursesConfig'

保存之后 刷新页面。效果如下:

用户登录、注册以及找回密码

登录

设置静态模板页面的方法

from django.urls import pathfrom django.views.generic import TemplateViewimport xadminurlpatterns = [path('xadmin/', xadmin.site.urls), # 选用xadmin# 静态页面模板path('', TemplateView.as_view(template_name='index.html'), name='index')]

操作app 下面的views.py文件

from django.shortcuts import renderfrom 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) # 验证失败返回 Noneif 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 renderfrom django.contrib.auth import authenticate # authenticate 鉴定from django.contrib.auth import login # 登录方法from django.contrib.auth import backendsfrom 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 userexcept Exception as e:return None

此时就可以用邮箱和密码登录了。

重写view的方法,采用Django基于类的方法书写各个功能模块的逻辑(比如login)

之前的(登录方式)的配制方法是基于函数的,下面要实现基于类的方法。

from django.views.generic.base import View # 基类

重写get 和 post 方法

from django.shortcuts import renderfrom django.contrib.auth import authenticate # authenticate 鉴定from django.contrib.auth import login # 登录方法from django.contrib.auth import backendsfrom 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) # 验证失败返回 Noneif 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 formsclass LoginForm(forms.Form): # 继承此类username = forms.CharField(required=True) # 说明此字段是必填字段password = forms.CharField(required=True, min_length=6) # 密码长度至少为6

然后再views.py中 类方法调用上面的LoginForm类

from django.shortcuts import renderfrom django.contrib.auth import authenticate # authenticate 鉴定from django.contrib.auth import login # 登录方法from django.contrib.auth import backendsfrom django.db.models import Q # 取并集from django.views.generic.base import View # 基类,实现基于类的方法 的类都要继承此基类from .models import UserProfilefrom .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()一直是falseprint(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) # 验证失败返回 Noneif 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, includeurlpatterns = [...re_path('^captcha/', include('captcha.urls')), # 注意 这里正则表达式没有结尾的$符号,否则报错]

step5: forms.py中:

from captcha.fields import CaptchaFieldclass 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>验&nbsp;证&nbsp;码</label>{{ register_form.captcha }}#模板中应用</div>

发送邮件业务逻辑

写一个发送邮件的基础函数

step1:在apps下面新建一个package文件夹,命名为utils,在此文件夹下新建send_email.py文件。(单放在package里面也方便其他阶段的调用。)内部代码如下:

发送确认链接邮件之前先把此链接的相关信息保存进数据库,因为用户点击确认链接时,需要在数据库中判断此链接是否是有效链接(即是否在数据库中存在。)

# -*- coding: utf-8 -*-__date__ = '/11/27 0:59'from random import Randomfrom django.core.mail import send_mail # 自动发邮件的函数from users.models import EmailVerifyRecordfrom MxOnline.settings import EMAIL_FROMdef random_str(random_length=8): # 默认随机字符串的长度为8ss = ''chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'length = len(chars) - 1random = Random()for i in range(random_length):ss += chars[random.randint(0, length)]return ssdef send_register_email(email, send_type="register"):email_record = EmailVerifyRecord() # 创建对象实例code = random_str(16) # 这里生成16位的随机字符串email_record.code = codeemail_record.email = emailemail_record.send_type = send_typeemail_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.' # 你邮箱的smtp域名地址EMAIL_PORT = 25 # 默认端口号EMAIL_HOST_USER = 'xxx@' # 你邮箱名字EMAIL_HOST_PASSWORD = 'xxx' # 邮箱密码EMAIL_FROM = 'xxx@' # 发件人,一般是上边邮箱名字

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()一直是falseif register_form.is_valid():user_name = request.POST.get('email', '')pass_word = request.POST.get('password', '')user_profile = UserProfile() # 定义一个实例对象user_profile.username = user_nameuser_profile.email = user_nameuser_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, EmailVerifyRecordclass 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.emailuser = 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, includefrom users import viewsimport xadminurlpatterns = [...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) # 验证失败返回 Noneif 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()一直是falseif 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_nameuser_profile.email = user_nameuser_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.emailuser = 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, includefrom django.views.generic import TemplateView # 使用静态模板 不需要在app下面的views.py文件中配置renderfrom users import viewsimport xadminurlpatterns = [...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 = codeemail_record.email = emailemail_record.send_type = send_typeemail_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, includefrom django.views.generic import TemplateView # 使用静态模板 不需要在app下面的views.py文件中配置renderfrom users import viewsimport xadminurlpatterns = [...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.emailreturn 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中设置一个有效期字段,表示链接是有有效期限制的。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。