用户模型设计+图形验证码3

用户模型设计

  • 当前用户系统必须要存入的字段:用户名,密码,手机号
  • 添加用户手机号字段,添加邮箱状态字段
  • auth_user中有create_superuser方法,把这个方法按自己意愿改
    - create_superuser方法把email字段去掉,添加mobile字段
  • 在settings.py文件中添加如下配置:
    #自定义用户模型
    AUTH_USER_MODEL = 'users.Users #app名.模型类名

users/views.py

from django.shortcuts import render
from django.views import View	#用模型类
#用到渲染模板和提交数据用类视图时比较多
class RegisterView(View):
    """
    #一般会注释url
    /register/
    """
    def get(self,request):
        return render(request,'users/register.html')

users/urls.py

from django.urls import path, re_path
from users import views
app_name = 'users'
urlpatterns = [
    path('login/',views.login,name='login'),
    #模型类的路径用views.类名.as_view()
    path('register/',views.RegisterView.as_view(),name='register'),
]

users/models.py

from django.db import models
#把UserManager类重命名_UserManager,用于重写
from django.contrib.auth.models import AbstractUser, UserManager as _UserManager
class UserManager(_UserManager):    #继承框架的UserManager
    """
    define user manager for modifing 'no need email'
    定义用于修改“无需电子邮件”的用户管理器当运行
    when 'python manager.py createsuperuser'python manager.py createsuperuser
    """
    #设置email的默认值为None
    def create_superuser(self,username,password,email=None,**extra_fields):
        """
        只是把email字段默认为空,方法里其他不变
        :param username:
        :param password:
        :param email:
        :param extra_fields:
        :return:
        """
        # super(UserManager,self).create_superuser(username=username,
        #沿用父类的方法
        super().create_superuser(username=username, #这行代码是python3中的写法,上行是python2中的写法   
                                 password=password,
                                 email=email,
                                 **extra_fields)
class Users(AbstractUser):		#AbstractUser是django的基类
    """
    add mobile,email_active fields to django users models
    """
    objects = UserManager()
    # A list of the field names that will be prompted for
    #通过createSuperUser管理命令创建用户时将提示输入的字段名称列表
    # when creating a user via the createsuperuser management command
    # REQUIRED_FIELS是AbstractUser提供的参数,
    # 除了用户名和密码是必须的,其他的想要添加的字段往参数里添加
    REQUIRED_FIELDS = ['mobile']
    # help_test在api接口文档中会用到
    # verbose_name在admin站点中会用到
    #增加手机号验证字段
    mobile = models.CharField(max_length=11,unique=True,verbose_name='手机号',
    #Django 模型中的verbose_name我们常常可能需要使用。
    #比如将数据库里面的数据导出成csv文件,那么csv文件的表头的名字可以通过取每个字段的verbose_name来获取,数据可以通过queryset语句来获取。
    # 这样制作出来的csv表就能想数据库一样,字段名和字段值一一对应了
                              help_text='手机号',
                              error_messages={'unique':'此手机号已被注册'}
                              ) #指定报错的中文信息
    # 邮箱状态默认为关闭,本项目不用邮箱验证,用手机号验证
    email_active = models.BooleanField(default=False,verbose_name='邮箱验证状态')
 #元类
    class Meta:
        db_table = 'tb_users'   #指明数据库表名
        verbose_name = '用户'     #在admin站点中显示的名称
        verbose_name_plural = verbose_name  #显示的复数名称
    def __str__(self):  #打印对象时调用
        return self.username

在这里插入图片描述

  • is_staff表示能不能登录admin
  • is_active表示是否是激活用户
  • 然后python manage.py makemigrations,python manage.py migrate
    在这里插入图片描述

用户注册功能

设计接口思路

  • 分析业务逻辑,明确在这个业务中需要涉及到几个相关子业务,将每个子业务当作一个接口来设计
  • 分析接口的功能任务,明确接口的访问方式与返回数据
    • 接口的请求方式,如GET,POST,PUT等
    • 接口的URL路径定义
    • 需要前端传递的数据及数据格式(如路径参数,查询字符串,请求体表单,JSON等)
    • 返回给前端的数据及数据格式

功能分析

  • 用户名判断是否存在
  • 手机号判断是否存在
  • 图片验证码
  • 短信验证码
  • 注册保存用户数据
    图片验证码,短信验证码考虑到后续可能会在其他业务中也用到,因此将验证码功能独立出来,创建一个新应用verifications,在此应用中实现图片验证码,短信验证码

图片验证码接口代码实现

图片验证码认证

  • 请求方法:GET
  • url定义:/image_codes/<uuid:image_code_id>/
  • 请求参数:url路径参数
参数类型前端是否必须传描述
image_codesuuid字符串图片验证码编号

后端视图实现

  • 直接借鉴大牛的代码,将生成图片验证码的模块文件夹(captcha)复制粘贴到项目根目录utils文件夹下
  • captcha下需要安装pillow,pip install pillow
    在这里插入图片描述
  • 由于验证(图片验证,短信验证)功能,以后有可能在其他应用或项目中重用,所以单独创建一个应用来实现所有验证相关的业务逻辑接口,在apps目录中创建一个verifications应用,并在settings.py文件中的INSTALLED_APPS列表中注册
    在这里插入图片描述
    在这里插入图片描述

captcha.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import random
import string
import os.path
from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL.ImageDraw import Draw
from PIL.ImageFont import truetype


class Bezier:
    def __init__(self):
        self.tsequence = tuple([t / 20.0 for t in range(21)])
        self.beziers = {}

    def pascal_row(self, n):
        """ Returns n-th row of Pascal's triangle
        """
        result = [1]
        x, numerator = 1, n
        for denominator in range(1, n // 2 + 1):
            x *= numerator
            x /= denominator
            result.append(x)
            numerator -= 1
        if n & 1 == 0:
            result.extend(reversed(result[:-1]))
        else:
            result.extend(reversed(result))
        return result

    def make_bezier(self, n):
        """ Bezier curves:
            http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
        """
        try:
            return self.beziers[n]
        except KeyError:
            combinations = self.pascal_row(n - 1)
            result = []
            for t in self.tsequence:
                tpowers = (t ** i for i in range(n))
                upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                coefs = [c * a * b for c, a, b in zip(combinations,
                                                      tpowers, upowers)]
                result.append(coefs)
            self.beziers[n] = result
            return result


class Captcha(object):
    def __init__(self):
        self._bezier = Bezier()
        self._dir = os.path.dirname(__file__)
        # self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')

    @staticmethod
    def instance():
        if not hasattr(Captcha, "_instance"):
            Captcha._instance = Captcha()
        return Captcha._instance

    def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
        # self.image = Image.new('RGB', (width, height), (255, 255, 255))
        self._text = text if text else random.sample(string.ascii_uppercase + string.ascii_uppercase + '3456789', 4)
        self.fonts = fonts if fonts else \
            [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
        self.width = width
        self.height = height
        self._color = color if color else self.random_color(0, 200, random.randint(220, 255))

    @staticmethod
    def random_color(start, end, opacity=None):
        red = random.randint(start, end)
        green = random.randint(start, end)
        blue = random.randint(start, end)
        if opacity is None:
            return red, green, blue
        return red, green, blue, opacity

    # draw image

    def background(self, image):
        Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
        return image

    @staticmethod
    def smooth(image):
        return image.filter(ImageFilter.SMOOTH)

    def curve(self, image, width=4, number=6, color=None):
        dx, height = image.size
        dx /= number
        path = [(dx * i, random.randint(0, height))
                for i in range(1, number)]
        bcoefs = self._bezier.make_bezier(number - 1)
        points = []
        for coefs in bcoefs:
            points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                for ps in zip(*path)))
        Draw(image).line(points, fill=color if color else self._color, width=width)
        return image

    def noise(self, image, number=50, level=2, color=None):
        width, height = image.size
        dx = width / 10
        width -= dx
        dy = height / 10
        height -= dy
        draw = Draw(image)
        for i in range(number):
            x = int(random.uniform(dx, width))
            y = int(random.uniform(dy, height))
            draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
        return image

    def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
        color = color if color else self._color
        fonts = tuple([truetype(name, size)
                       for name in fonts
                       for size in font_sizes or (65, 70, 75)])
        draw = Draw(image)
        char_images = []
        for c in self._text:
            font = random.choice(fonts)
            c_width, c_height = draw.textsize(c, font=font)
            char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
            char_draw = Draw(char_image)
            char_draw.text((0, 0), c, font=font, fill=color)
            char_image = char_image.crop(char_image.getbbox())
            for drawing in drawings:
                d = getattr(self, drawing)
                char_image = d(char_image)
            char_images.append(char_image)
        width, height = image.size
        offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                  for i in char_images[:-1]) -
                      char_images[-1].size[0]) / 2)
        for char_image in char_images:
            c_width, c_height = char_image.size
            mask = char_image.convert('L').point(lambda i: i * 1.97)
            image.paste(char_image,
                        (offset, int((height - c_height) / 2)),
                        mask)
            offset += int(c_width * squeeze_factor)
        return image

    # draw text
    @staticmethod
    def warp(image, dx_factor=0.27, dy_factor=0.21):
        width, height = image.size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int(random.uniform(-dx, dx))
        y1 = int(random.uniform(-dy, dy))
        x2 = int(random.uniform(-dx, dx))
        y2 = int(random.uniform(-dy, dy))
        image2 = Image.new('RGB',
                           (width + abs(x1) + abs(x2),
                            height + abs(y1) + abs(y2)))
        image2.paste(image, (abs(x1), abs(y1)))
        width2, height2 = image2.size
        return image2.transform(
            (width, height), Image.QUAD,
            (x1, y1,
             -x1, height2 - y2,
             width2 + x2, height2 + y2,
             width2 - x2, -y1))

    @staticmethod
    def offset(image, dx_factor=0.1, dy_factor=0.2):
        width, height = image.size
        dx = int(random.random() * width * dx_factor)
        dy = int(random.random() * height * dy_factor)
        image2 = Image.new('RGB', (width + dx, height + dy))
        image2.paste(image, (dx, dy))
        return image2

    @staticmethod
    def rotate(image, angle=25):
        return image.rotate(
            random.uniform(-angle, angle), Image.BILINEAR, expand=1)

    def captcha(self, path=None, fmt='JPEG'):
        """Create a captcha.

        Args:
            path: save path, default None.
            fmt: image format, PNG / JPEG.
        Returns:
            A tuple, (text, StringIO.value).
            For example:
                ('JGW9', '\x89PNG\r\n\x1a\n\x00\x00\x00\r...')

        """
        image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
        image = self.background(image)
        image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
        image = self.curve(image)
        image = self.noise(image)
        image = self.smooth(image)
        text = "".join(self._text)
        out = BytesIO()
        image.save(out, format=fmt)
        return text, out.getvalue()

    def generate_captcha(self):
        self.initialize()
        return self.captcha("")


captcha = Captcha.instance()	#verifications的views.py文件导入此方法

if __name__ == '__main__':
    print(captcha.generate_captcha())

  • 运行captcha.py,得到下图所示,是一个元组,形如:(‘文本’,‘图片’)
    在这里插入图片描述

verifications/views.py

from django.shortcuts import render
from django.http import HttpResponse
from django_redis import get_redis_connection	#连接redis数据库
from utils.captcha.captcha import captcha    #utils/captcha/captcha.py   captcha实例
from django.views import View
from apps.verifications import constants
import logging
#导入settings中的日志器
logger = logging.getLogger('django')
class ImageCode(View):
    """
    /image_codes/<uuid:image_code_id>/
    图形验证码需要标号后存入redis,用于匹配
    前端请求方法:get
    """
    def get(self,request,image_code_id):
        # 见captcha.py的最后一行,即输出的元组
        text,image = captcha.generate_captcha() #图片展示给用户看,text要临时存入数据库
        con_redis = get_redis_connection(alias='verity_codes')  #连接指定的redis库
        # img_key = 'img_{}'.format(image_code_id)    #把uuid拼接成img_xxx形式
        img_key = f'img_{image_code_id}'    #等同于上行代码
        # con_redis.setex(img_key,300,text)    #暂存300s
        # 把带uuid的拼接形式,暂存时间,验证码的text文本形式保存进redis
        con_redis.setex(img_key,constants.IMAGE_CODE_REDIS_EXPIRES,text)    #常量一般单独放置,尽量不改源代码,方便以后修改维护
        logger.info('Image_code: {}'.format(text))  #后台日志直接打印,方便调试
        return HttpResponse(content=image,content_type='image/jpg')

在这里插入图片描述
在这里插入图片描述

  • 在settings.py文件中加入如下代码:
# 将用户的session保存到redis中
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# 指定缓存redis的别名
SESSION_CACHE_ALIAS = "session"

verifications/urls.py

from django.urls import path, re_path
from verifications import views
app_name = 'verifications'
urlpatterns = [
	path('image_codes/<uuid:image_code_id>/',views.ImageCode.as_view(),name='verifications'),
]
  • 在web_prv/urls.py中添加verifications.urls

users/register

  • 两处改动
    • 验证码的img标签的src改为空
    • 在结尾加上auth.js文件
{% extends 'base/base.html' %}
  {% block title %}RegisterPage{% endblock %}
  {% block link %}<link rel="stylesheet" href="{% static 'css/authPro/auth.css' %}">{% endblock %}
<!-- container start -->
{% block main-start %}
<main id="container">
  <div class="register-contain">
    <div class="top-contain">
      <h4 class="please-register">请注册</h4>
      <a href="javascript:void(0);" class="login">立即登录 &gt;</a>
    </div>
    <form action="" method="post" class="form-contain">
      <div class="form-item">
        <input type="text" placeholder="请输入用户名" id="user_name" name="username" class="form-control" autocomplete="off">
      </div>
      <div class="form-item">
        <input type="password" placeholder="请输入密码" id="pwd" name="password" class="form-control">
      </div>
      <div class="form-item">
        <input type="password" placeholder="请输入确认密码" name="password_repeat" class="form-control">
      </div>
      <div class="form-item">
        <input type="tel" placeholder="请输入手机号" id="mobile" name="telephone" class="form-control" autocomplete="off" autofocus>
      </div>
      <div class="form-item">
        <input type="text" placeholder="请输入图形验证码" id="input_captcha" name="captcha_graph" class="form-captcha">
        <a href="javascript:void(0);" class="captcha-graph-img">
          <img src="" alt="验证码" title="点击刷新">
        </a>
      </div>
      <div class="form-item">
        <input type="text" placeholder="请输入短信验证码" name="sms_captcha" class="form-captcha" id="input_smscode" autocomplete="off">
        <a href="javascript:void(0);" class="sms-captcha" title="发送验证码">获取短信验证码</a>
      </div>
      <div class="form-item">
        <input type="submit" value="立即注册" class="register-btn">
      </div>
    </form>
  </div>
</main>
{% endblock %}
<!-- container end -->
{% block domready %}
	<script src="{% static 'js/users/auth.js' %}"></script>
{% endblock %}
  • 每生成一张图形验证码就在数据库存上对应的text,对每一张图形验证码进行编号,使其与对应的text为一组,以便刷新多个验证码时可以对应上
  • 图形验证码是给客户看的,对应的text是和客户输入的验证码进行对比的
  • 在static/js/新建文件夹users,在users中新建auth.js
  • auth.js
$(function () {
  let $img = $(".form-item .captcha-graph-img img");  // 获取图像标签
  let sImageCodeId = "";  // 定义图像验证码ID值
  generateImageCode();  // 生成图像验证码图片
  $img.click(generateImageCode);  // 点击图片验证码生成新的图片验证码图片
  // 生成一个图片验证码的编号,并设置页面中图片验证码img标签的src属性
  function generateImageCode() {
    // 1、生成一个图片验证码随机编号
    sImageCodeId = generateUUID();
    // 2、拼接请求url /image_codes/<uuid:image_code_id>/
    let imageCodeUrl = "/image_codes/" + sImageCodeId + "/";
    // 3、修改验证码图片src地址
    $img.attr('src', imageCodeUrl)
  }
  // 生成图片UUID验证码
  function generateUUID() {
    let d = new Date().getTime();
    if (window.performance && typeof window.performance.now === "function") {
        d += performance.now(); //use high-precision timer if available
    }
    let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        let r = (d + Math.random() * 16) % 16 | 0;
        d = Math.floor(d / 16);
        return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
    });
    return uuid;
  }  
});
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值