栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

Django轻量级任务追踪管理平台开发:一

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Django轻量级任务追踪管理平台开发:一

学习django怎么能没有项目练手?最近在B站捞了一个不错的实战项目,是个人比较喜欢的老师 武沛齐录制的。通过将重要的知识点记录下来,达到强化理解的作用。

一、项目展示


主页看着好炫有没有?但据说只是一张图片,哈哈哈>_<


所有状态栏都是可以点的,功能看着还挺多的。看着不错?开始干吧!

二、阶段目标与技术点

今天先完成的是最基本的注册和登录。注册时会用到短信验证码,登录会用到图片验证码。所需要的主要知识点如下:

  • 腾讯云短信发送验证码

  • redis存储验证码

  • pillow模块生成图片验证码

  • 由于不用django自带的用户表,因此会用到session和中间件middleware,以及用md5对密码进行加密

  • mysql+bootstrap

  • ajax

三、代码展示与介绍 1. 创建项目与目录结构

为了能使当前的app与之后可能的app有效分隔,templates和static都包含在app内部。系统查找templates和static会优先在项目根目录寻找,若找不到则会根据app注册的顺序,进入每个app分别查找。
创建一个tracer项目,新建一个app01,主要目录结构如下:

主要目录结构

│  manage.py
│  requirements.txt     
├─app01
│  │  admin.py
│  │  apps.py
│  │  forms.py
│  │  models.py
│  │  tests.py
│  │  urls.py
│  │  __init__.py
│  │         
│  ├─middleware
│  │  │  account.py
│  │  │  __init__.py
│  │  │  
│  │  │        
│  ├─migrations
│  │  │  __init__.py
│  │  │  
│  │  │      
│  ├─static
│  │  ├─css
│  │  ├─fonts     
│  │  ├─image
│  │  └─js
│  ├─templates
│  │  └─layout
│  │          base.html
│  │          index.html
│  │          login.html
│  │          login_sms.html
│  │          register.html
│  │          
│  ├─utils
│  │  │  common.py
│  │  │  imgcode.py
│  │  │  sms.py
│  │          
│  ├─views
│  │  │  __init__.py
│  │  │  
│  │  ├─account
│  │  │  │  views.py
│  │  │  │  __init__.py 
│  │  │          
├─tracer
│  │  asgi.py
│  │  local_settings.py
│  │  settings.py
│  │  urls.py
│  │  wsgi.py
│  │  __init__.py
│  │      
├─venv

2. 模型表

模型表很简单,只有一个用户表,目前没有写任何关联字段。

# app01/models.py
from django.db import models

class UserInfo(models.Model):
    username = models.CharField(verbose_name='用户名', max_length=32, db_index=True)
    password = models.CharField(verbose_name='密码', max_length=32)
    email = models.EmailField(verbose_name='邮箱')
    phone = models.CharField(verbose_name='手机号', max_length=16)

    def __str__(self):
        return self.username

3. 路由

虽然目前只有一个app,但为了模拟之后复杂的多app的场景,需要使用路由分发,并且为每一个app创建一个命名空间。

# tracer/urls.py

urlpatterns = [
    path('admin/', admin.site.urls),
    # 增加路由分发,命名空间
    # 注意若想添加命名空间,include后面需要跟一个元组,把app传进去
    path('app01/', include(('app01.urls', 'app01'), namespace='app01')),
]


# app01/urls.py
urlpatterns = [
    # 注册
    path('register/', views.register, name='register'),
    # 发送短信
    path('send/sms/', views.send_sms, name='send_sms'),
]

4. 注册功能实现

表模型创建好之后,就可以开始实现注册功能了。思路是用ModelForm进行数据验证和前端渲染,注册时输入手机号获取验证码,若没有问题则将数据传入视图函数中,然后直接入库。若数据校验出现问题,前端需要实时渲染,因此还需要用到Ajax.

forms中的代码

# app01/forms.py

import ...

# 定义一个类,后面的表单类如果想要bootstrap样式form-control或者一些默认的提示,可以继承这一个类
class BootstrapForm:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            field.widget.attrs["class"] = "form-control field"
            field.widget.attrs["placeholder"] = '请输入'+field.label
            field.error_messages['required'] = '此字段必须填写'


class RegisterForm(BootstrapForm, forms.ModelForm):
    # 需要验证的字段全部重新写一遍就好
    username = forms.CharField(label='用户名', min_length=3, max_length=6, error_messages={'min_length': '此字段最少3位字符', 'max_length': '此字段最多6位字符'}
    )
    # ...
    # 表模型中没有code和confrim_password,只做数据校验用
    confirm_password = forms.CharField(label='确认密码', min_length=3, widget=forms.PasswordInput(), error_messages=              
    {'min_length': '密码至少3位'
    })
    code = forms.CharField(label='验证码')
    

    # 下面是钩子函数,验证码若超时或错误要有错误提示,密码若没有问题应该加密返回,这样视图函数就可以直接存储了
    # ...    
    def clean_password(self):
        """加密处理"""
        password = self.cleaned_data.get("password")
        # encoder()是自己写的加密函数
        return encoder(password)

加密函数

# app01/utils/common.py

import hashlib
from django.conf import settings

def encoder(password):
    """MD5加密,settings.SECRET_KEY加盐"""
    if password:
        hash_object = hashlib.md5(settings.SECRET_KEY.encode('utf-8'))
        hash_object.update(password.encode('utf-8'))
        return hash_object.hexdigest()

前端使用bootstrap简单做个样式就行。由于表单中字段比较多,如果后期通过ajax一一获取input框中的数据会比较繁琐,因此使用form包裹一下,然后给form一个id,这样提交时只需要调用$(#regForm).serialize()就可以一次性获得所有input标签的内容了。

前端部分代码

{% for field in form %}{{ field.label }}{% if field.name == 'code' %}
      {{ field }}{% else %}
          {{ field }}            {% endif %}{% endfor %}

前端搭建好之后可以开始使用了。

第一步,点击获取验证码,直接通过腾讯云短信发送验证码到手机上。关于腾讯云短信的使用,武sir在自己的网站有介绍,有感兴趣的同学可以去看看 : https://pythonav.com/wiki/detail/10/81/

为了让项目更安全,短信的配置连同mysql都应该放在local_settings中,推送或上传时不要让别人看到!

点击按钮之后应该会出现倒计时效果,手机号会通过ajax发到后端。

发送验证码代码

let timer;
function bindSendSMSEvent(){                        
      $("#get_sms").click(function () {  $(".error-msg").empty();
      // 未输入手机号
      if (!$("#id_phone").val()){  alert("请输入手机号!")
          return;
      }
      // 不能重复发送验证码
      console.log(timer);
      if (timer!==undefined){  return;
      }              
      let that = $(this);
      $.ajax({  // 路由中使用了命名空间,因此反向解析路由是要加上app:前缀
          url: "{% url 'app01:send_sms' %}",
          type: 'get',
          dataType: 'json',
          data: {'phone': $("#id_phone").val(),'tpl':'register'},
          success: function (ret) {  // 后台发送成功,显示倒计时
              console.log(ret);
              if (ret["status"]){  // 调用页面倒计时的显示
                  sendSmsReminder(that);
              }else{  // 显示错误信息
                  $.each(ret["msg"], function (key, value) {  $("#id_"+key).next().text(value);
                      $("label[for='id_"+key+"']").parent().addClass("has-error");
                  })
              }
          }
      })
  })
}
// 页面倒计时效果
function sendSmsReminder(self) {let time = 60;
    self.addClass("disabled");
    timer = setInterval(function () {time --;
        self.val(time+"秒后重新获取");
        if (time===0){self.removeClass("disabled").val("获取验证码");
            clearInterval(timer);
            timer = undefined;
        }
    },1000)
}

这里要注意处理一个bug,那就是若频繁点击获取验证码,就会启动多个定时器,倒计时效果就会完全乱掉。处理方法是在函数外部定义一个timer,并规定只有timer是undefined才能往下走。这样当一个定时器启动之后,timer就不再是undefined了,再点击获取验证码就被直接return,不会触发定时器了。

后端将手机号和短信的模板发给form进行校验。ajax会这样发送数据:

$.ajax({url: "{% url 'app01:send_sms' %}",
    type: 'get',
    dataType: 'json',
    // 这里除了手机号,还要传一个tpl变量
    data: {'phone': $("#id_phone").val(),'tpl':'register'},
    success: function (ret) {...}
    })

若是注册,tpl传一个’register’,若后期短信登录,则传一个’login’。不同的单词对应不同的短信模板,毕竟注册和登录的短信模板不能相同。模板需要在腾讯云短信的界面和django代码中配置。因此tpl的值是否在我们短信的配置中,也需要验证。
但是由于tpl这个字段不在form表单中(若放在表单中,则前端也会为tpl渲染出一个input框,这显然是不合理的),因此表单中为了获取tpl就需要从request对象中拿。也就是说后端需要将request传给form表单。

# app01/views/account/views.py
# 视图函数

def send_sms(request):
    """发送短信"""
    ret = {"status": 1, "msg": ""}
    # 将request传入
    form = SendSmsForm(request, request.GET)
    if not form.is_valid():
        ret["status"] = 0
        ret["msg"] = form.errors
    return JsonResponse(ret)


# app01/forms.py

class SendSmsForm(forms.Form):
    """发送验证码时验证手机"""
    phone = forms.CharField(label='手机号', validators=[
        RegexValidator(r'^1(3d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8d|9[0-35-9])d{8}$', '手机号格式错误'),])
    
    # 通过重写init方法获得request
    def __init__(self, request, *args, **kwargs):
        """为验证tpl传入request"""
        super(SendSmsForm, self).__init__(*args, **kwargs)
        self.request = request

接下来如果手机号没有问题,就可以生成随即验证码,然后存入redis中了。
redis使用django-redis,使用前需要先下载redis,然后执行pip install django-redis,最后在django中配置一下redis的参数就可以了。想要详细了解的话仍然可以看武sir: https://pythonav.com/wiki/detail/10/82/

# app01/forms.py

class SendSmsForm(forms.Form):
    # ...
    def clean_phone(self):        
        # 验证短信模板是否有问题
        tpl = self.request.GET.get("tpl")
        tpl_id = local_settings.SMS_TEMPLATE.get(tpl)
        if not tpl_id:
            raise ValidationError("短信模板错误")
        phone = self.cleaned_data["phone"]
        # 判断手机号是否存在
        exist = models.UserInfo.objects.filter(phone=phone).exists()
        if exist:
            raise ValidationError('手机号已存在')
        # 生成验证码
        code = random.randint(1000, 9999)
        print(code)
        sms = send_sms_single(phone, tpl_id, [code,])
        # 发送成功result是0,只要不是0就是发送失败了
        if sms["result"] != 0:
            raise ValidationError("短信发送失败,{}".format(sms['errmsg']))
        # 发送成功,将验证码写入redis
        conn = get_redis_connection()
        conn.set(phone, code, 600)
        return phone

前端将收到的验证码输入表单中,然后点击注册,再次提交表单。这里再次使用ajax提交,因为如果直接使用form提交,页面的倒计时效果会因为刷新而消失。虽然可以通过进一步的处理来解决,但是会麻烦很多。

表单提交之后再次传入form中验证,用户名、密码、邮箱的校验比较简单,主要是验证码稍微繁琐。

def clean_code(self):
    """验证码是否匹配"""
    code = self.cleaned_data.get("code")
    phone = self.cleaned_data.get("phone")
    # 建立redis连接
    conn = get_redis_connection()
    # 查询手机号是否存在,如果不存在,可能过期了,也可能根本就没有发送过
    redis_code_byte = conn.get(phone)
    if redis_code_byte:
        # redis存储的是字节类型,需要解码
        redis_code = redis_code_byte.decode("utf-8")
        if code != redis_code:
            self.add_error("code", "验证码错误")
    else:
        self.add_error("code", "验证码错误或已过期,请重新获取")
    return code

表单验证通过之后,视图函数中就可以直接入库了!使用modelform有一个好处就是不需要手动将无用的数据,比如confirm_password,code剔除,django内部已经帮我们做了。直接save()就大功告成了!

def register(request):
    ret = {"status": 1, "msg": ''}
    if request.method == 'POST':
        # 注册请求
        form = RegisterForm(request.POST)
        if form.is_valid():
            # 表单验证通过
            print(form.cleaned_data)
            form.save()
            ret["url"] = '/app01/login'
        else:
            # 表单验证失败
            ret["status"] = 0
            ret["msg"] = form.errors
        return JsonResponse(ret)
    else:
        form = RegisterForm()
    return render(request, 'layout/register.html', {'form': form})

至此,注册功能就完成了,接下来是登录的实现。

5. 登录功能实现

最常见的登录方式有两种:

  • 短信登录

  • 密码登录

1. 短信登录

以上两种我们都要实现,首先是短信登录。其实短信登录和短信注册的逻辑几乎一样,模板甚至可以直接copy注册时的。只需在forms中重写一个类,然后传到前端进行渲染,就可以显示出类似注册界面的登录界面。

唯一与注册有区别的一点在于,当手机号不存在时,应该提示报错。而注册时手机号已存在才会报错。
因此froms中应该稍微修改一下:

#app01/forms.py
class SendSmsForm(forms.Form):
    # ...
    exist = models.UserInfo.objects.filter(phone=phone).exists()
    # 注册时
    if tpl == 'register':
        if exist:
            raise ValidationError('手机号已存在')
    # 登录时
    elif tpl == 'login':
        if not exist:
            raise ValidationError('手机号不存在,请先注册')
    # 生成验证码
    code = random.randint(1000, 9999)
    # ...

验证成功之后,需要将当前用户存入session中,并添加一个超时时间。

# app01/views/account/views.py

def login_sms(request):
    """短信登录"""
    ret = {"status": 1, "msg": ''}
    if request.method == 'POST':
        form = LoginSmsForm(request.POST)
        if form.is_valid():
            # 将用户信息写入session,登录成功之后使用
            user = form.cleaned_data.get('phone')
            request.session['user_id'] = user.id
            request.session['user_name'] = user.username
            # session过期时间要重新设置
            request.session.set_expiry(60*3600*24*7)
            ret['url'] = '/app01/index'
        else:
            ret['status'] = 0
            ret['msg'] = form.errors
        return JsonResponse(ret)
    else:
        form = LoginSmsForm()
    return render(request, "layout/login_sms.html", {"form": form})

这样,手机号登录就完成了。

2. 密码登录

密码登录需要输入的是用户名、密码和验证码,界面如图所示:

密码登录最复杂的就是生成图片验证码。这里使用pillow模块。关于pillow如何生成图片,继续参考武sir的博客:https://www.cnblogs.com/wupeiqi/articles/5812291.html
验证码的思路是:给前端的img标签一个url地址,这样它就会再次向服务器发请求,这时我们调用写好的生成验证码的函数将图片生成,通过BytesIO写入内存中,最后再从内存中读取这个图片,返还给前端。


# 后端调用生成验证码的函数,将验证码返回

def get_img(request):
    """生成验证码"""
    from app01.utils.imgcode import check_code
    # check_code()就是生成验证码的函数
    img, code = check_code()
    print(code)
    # 验证码写入session
    request.session['code'] = code
    # 设置图片验证码超时时间为2分钟
    request.session.set_expiry(120)
    # 将图片写入内存
    from io import BytesIO
    io_obj = BytesIO()
    img.save(io_obj, 'png')
    return HttpResponse(io_obj.getvalue())

最后登录时进行校验即可。这里我们的用户名使用邮箱或手机号,也就是邮箱号+密码或手机号+密码都可以。需要用到Q查询。

# app01/views/account/views.py

def login(request):
    # ...
    user_obj = models.UserInfo.objects.filter(Q(email=username) | Q(phone=username)).filter(
                password=password).first()
    if not user_obj:
                form.add_error("username", "用户名或密码错误")
    # ...

这些问题解决了之后,就可以完成密码登录了。

6. 登录检测显示

若用户未登录,导航条应该显示“登录”和“注册”,若用户已经登录,导航条应该显示用户名和更多操作:


为了判断用户的登录状态,需要用到中间件middleware。在请求来之前判断session中是否存在数据,若存在则表示用户已登录,于是将此用户对象传入request。

# app01/middleware/account.py

class LoginMiddleware(MiddlewareMixin):
    def process_request(self, request):
        user_id = request.session.get("user_id")
        request.tracer = UserInfo.objects.filter(id=user_id).first()
        print(request.tracer)

前端做一个判断即可:

{% if request.tracer %}登出{{ request.tracer.username }}用户资料后台管理{% else %}注册登录{% endif %}

至此,登录功能也全部实现!

四、总结

今天主要完成了注册和登录功能,虽然逻辑比较简单但也还是有一些重要的知识点的。之后我会跟着老师继续前进,进行第二部分:后台管理的一些列操作。各位若有什么意见或建议,欢迎留言哦。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1025966.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号