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_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'

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

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

  1.  登录

设置静态模板页面的方法

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>验&nbsp;证&nbsp;码</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中设置一个有效期字段,表示链接是有有效期限制的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值