主页 > 互联网  > 

Django项目之订单管理part1

Django项目之订单管理part1
一.前言

我们前面把django的常用知识点给讲完了,现在我们开始项目部分,项目是一个订单管理系统,我们同时也会在项目之中也会讲一些前面没有用到的知识点。

项目大概流程如下:

核心的功能模块:

认证模块,用户名密码 或 手机短信登录(60s有效)。

角色管理,不同角色具有不同权限 和 展示不同菜单。

管理员,充值 客户,下单

客户管理,除了基本的增删改查以外,支持对客户可以分级,不同级别后续下单折扣不同。

交易中心

管理员可以给客户余额充值/扣费

客户可以下单/撤单

生成交易记录

对订单进行多维度搜索,例如:客户姓名、订单号。

worker,去执行订单并更新订单状态。

二.单点知识 2.1 群发短信

这个需要我们去开通腾讯云短信服务,具体大家浏览器搜索就知道了,参考下面:

短信 Python SDK-SDK 文档-文档中心-腾讯云 cloud.tencent /document/product/382/43196 cloud.tencent /document/product/382/43196 cloud.tencent /document/product/382/43196 cloud.tencent /document/product/382/43196 cloud.tencent /document/product/382/43196

大概精简下来就长这样 

# -*- coding: utf-8 -*- from tencentcloud mon import credential from tencentcloud.sms.v20210111 import sms_client, models # SecretId和SecretKey cred = credential.Credential("SecretId", "SecretKey") client = sms_client.SmsClient(cred, "ap-guangzhou") req = models.SendSmsRequest() # 设置的appid req.SmsSdkAppId = "appid" # 设置的签名内容 req.SignName = "签名内容" # 模板id req.TemplateId = "模板id" # 验证码内容 req.TemplateParamSet = ["666666"] req.PhoneNumberSet = ["+8615888888888"] resp = client.SendSms(req) print(resp) 2.2 权限和菜单管理 2.2.1 菜单

不同的用户登陆时会看到不同的菜单,那我们此时此刻就有几种选择方式,一是写到html的母版里面,但是这样的弊端就是后续更改页面位置太多会比较麻烦,我们也可以把菜单的设计放在settings里面或者数据库里面。

页面写死 HTML模板:

<html>     {% if 角色 "管理员"%}         <a href="/xxx/x">用户管理</a>         <a href="/xxx/x">级别管理</a>         <a href="/xxx/x">级别管理</a>         ...     {% else %}         <a href="/xxx/x">xxx管理</a>         <a href="/xxx/x">级别管理</a>     {% endif %} </html>

将菜单放在配置文件中:

# settings.py

ADMIN = [     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." }, ]

USER = [     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." },     {"title":"用户管理", "url":"...." }, ]

<html>     {% if 角色 "管理员"%}         {% for item in ADMIN%}             <a href="{{item.url}}">{{item.title}}</a>         {%emdfor%}     {% else %}         {% for item in USER%}             <a href="{{item.url}}">{{item.title}}</a>         {%emdfor%}     {% endif %} </html> 

将 菜单+角色 写入数据库 :

idtitleurl1用户管理...2级别管理...3订单管理... id角色1管理员2用户 idrole_idmenu_id111212322 在页面展示数据库 1.查询特定角色关联的所有的菜单 2.在页面上进行展示

 如果选择使用配置文件的方式:

ADMIN = [     {         "title":"用户管理",          "children":[             {"title":"级别列表","url":"....", "name":"level_list",}             {"title":"级别列表","url":"...."}             {"title":"级别列表","url":"...."}         ]     },     {         "title":"订单管理",          "children":[             {"title":"订单列表","url":"...."}             {"title":"订单列表","url":"...."}             {"title":"订单列表","url":"...."}         ]     }, ]

 这个name是我们可以通过name反向生成ur,在前面路由里面和大家说过

菜单选中和展开问题:

1.获取当前用户请求的 URL   pricepolicy/list/ 或 url对应的name

2. pricepolicy/list/ 配置 ADMIN中的URL   ->默认选中

路径导航的问题 :

1.获取当前用户请求的 URL   pricepolicy/list/ 或 url对应的name

2.获取上级,展示导航信息

3.设置菜单与下级关系

扩展:菜单多级关系

idtitleurlparent_id1客户管理nullnull2级别列表...13客户列表...14订单管理nullnull5价格...46交易...47其他...6

注意:类似与平台的评论。

idcontentroot_idparent_iddepth1优秀nullnull02不咋样nullnull03确实1114哈哈1115你说的都对132

- 优秀     确实         你说的都对     哈哈 - 不咋样 

2.2.2 权限

我们在权限的判断时,应该要两种判断,一种是客户点击页面,一种是导航条输入api,都要进行判断,我们一般就是把客户和他对应的url存放到字典或者集合,每次访问都在中间件进行判断,不用列表是因为列表的查询效率特别的低特别的耽误时间

文件settings.py的方式:

admin_permisions = {     "level_list":{...},     "level_edit":{..., 'parent':'level_list'},     "level_add":{... 'parent':'level_list'},     "level_delete":{..'parent':'level_list'.},          "user_list":{...},     "user_edit":{...},     "user_add":{...},     "user_delete":{...}, }

user_permisions = {     ... }

当然我们通常不会存储链接,而是存储name和naspace

admin访问某个URL + 路由信息(name、namespace),获取当前的URL  /level/edit/4/ -> 是否存在URL,因为有的url是可变的,比如编辑删除,他的客户信息都是动态的

在中间件中根据URL中的name进行权限的校验。

数据库方式:

2.3 队列 

rabbitMQ,Linux命令+服务构建+python代码。

kafka,Linux命令+服务构建+python代码。

redis的列表

基于redis实现上述的过程和代码示例:

安装redis

启动redis

这里我就不说安装教程了,大家不记得直接去搜就好了

Python操作redis:

pip install redis 

import redis conn = redis.Redis(host='127.0.0.1', port=6379, encoding='utf-8') # 短信验证码 conn.set('15131255089', 9999, ex=10) value = conn.get('15131255089') print(value) import redis conn = redis.Redis(host='127.0.0.1', port=6379, encoding='utf-8') # 放值 # conn.lpush('my_queue', "root") # conn.lpush('my_queue', "good") # 取值 v1 = conn.brpop("my_queue", timeout=5) print(v1)

这是两种实例代码,上面是我们用来短信验证码的,而下面就是我们的任务队列

2.4 worker和线程池  # 1.去redis中获取任务 import time # 2.再将此订单在数据库中的状态修改为 执行中 # 3.获取任务详细:100个任务 # 4.线程池或协程 from concurrent.futures import ThreadPoolExecutor def task(arg): # 执行任务 # 模拟运行时间 time.sleep(1) start_time=time.time() pool = ThreadPoolExecutor(50) for i in range(100): pool.submit(task, "任务参数") pool.shutdown() # 卡主,等待所有的任务执行完毕。 end_time=time.time() # 5.更新订单状态,已完成 print(end_time-start_time)

这个就是前面说的多线程

三.项目 

我们这里完整的说一下整个项目的创建,不记得的可以看前面几篇,有更具体的介绍

1.我们新创建一个项目django_project,然后安装django3.2

2.执行命令django-admin startproject django_project .

3.这里我们只创建一个app,我们就叫web,执行命令python manage.py startapp web

4.连接数据库,pip install pymysql,再在项目同名的文件夹下的__init__.py写入

import pymysql pymysql.install_as_MySQLdb() 然后再在settings改成mysql的配置

5.屏蔽掉settings里面没用的组件,让项目是一个纯净版的django(可不做)

6.再将app注册

3.1 表结构设计

表结构设计我们要有管理员表,客户表,级别表(折扣等级),价格表,订单表,交易记录表。

这里我就直接给代码了,代码也都给上注释了

from django.db import models class ActiveBaseModel(models.Model): """用来继承的表""" active = models.SmallIntegerField(verbose_name='状态', default=1, choices=((1, '激活'), (0, '删除'))) # 用来进行逻辑删除而不是物理删除 class Meta: abstract = True # 把 ActiveBaseModel 设置为抽象类,防止在数据库中创建表 class Administrator(ActiveBaseModel): """管理员表""" username = models.CharField(verbose_name='用户名', max_length=32, db_index=True) password = models.CharField(verbose_name='密码', max_length=64) mobile = models.CharField(verbose_name='手机号', max_length=11, db_index=True) create_date = models.DateTimeField(verbose_name='创建日期', auto_now_add=True) # auto_now_add和之前说的auto_now的区别是前者只在创建的时候自动生成,后者在后面更新的时候也会自动更新,我们这里要的是第一次创建的 class Level(ActiveBaseModel): """级别表""" title = models.CharField(verbose_name='标题', max_length=32) percent = models.IntegerField(verbose_name='折扣') class Customer(ActiveBaseModel): """客户表""" username = models.CharField(verbose_name='用户名', max_length=32, db_index=True) password = models.CharField(verbose_name='密码', max_length=64) mobile = models.CharField(verbose_name='手机号', max_length=11, db_index=True) balance = models.DecimalField(verbose_name='账户余额', default=0, max_digits=10, decimal_places=2) level = models.ForeignKey(verbose_name='级别', to='Level', on_delete=models.CASCADE) creator = models.ForeignKey(verbose_name='创建者', to='Administrator', on_delete=models.CASCADE) create_date = models.DateTimeField(verbose_name='创建日期', auto_now_add=True) class PricePolicy(models.Model): """价格策略 (原价,后续可以根据级别享受不同折扣) 1000 5 2000 8 """ count = models.IntegerField(verbose_name="数量") price = models.DecimalField(verbose_name='价格', default=0, max_digits=10, decimal_places=2) class Order(ActiveBaseModel): """订单表""" status_choices = ( (1, '待执行'), (2, '正在执行'), (3, '已完成'), (4, '失败'), ) status = models.SmallIntegerField(verbose_name='状态', choices=status_choices, default=1) oid = models.CharField(verbose_name='订单号', max_length=64, unique=True) count = models.IntegerField(verbose_name='数量') price = models.DecimalField(verbose_name='价格', default=0, max_digits=10, decimal_places=2) # 价格这里我们不通过价格策略关联,因为如果我们后期更换价格策略,会对不上账 real_price = models.DecimalField(verbose_name='实际价格', default=0, max_digits=10, decimal_places=2) old_view_count = models.CharField(verbose_name="原播放量", max_length=32, default="0") # 不用整型是因为我们拿到的播放量很容易是字符串例如1.2w create_date = models.DateTimeField(verbose_name='创建日期', auto_now_add=True) customer = models.ForeignKey(verbose_name='客户', to="Customer", on_delete=models.CASCADE) memo = models.TextField(verbose_name='备注', null=True, blank=True) class TransactionRecord(ActiveBaseModel): """交易记录""" charge_type_class_mapping = { 1: 'success', 2: 'danger', 3: 'default', 4: 'info', 5: 'primary', }#这个是根据bootstrap的按钮样式来做的一个字典 charge_type_choices=((1,"充值"),(2,"扣款"),(3,"创建订单"),(4,"删除订单"),(5,"撤单"),) charge_type=models.SmallIntegerField(verbose_name="类型",choices=charge_type_choices) customer=models.ForeignKey(verbose_name='客户',to='Customer',on_delete=models.CASCADE) amount=models.DecimalField(verbose_name='金额',default=0,max_digits=10,decimal_places=2) creator=models.ForeignKey(verbose_name='管理员',to='Administrator',on_delete=models.CASCADE,null=True,blank=True) order_oid=models.CharField(verbose_name='订单号',max_length=64,null=True,blank=True,db_index=True)

这里就是表结构的代码,大家可以看看

3.2 用户认证相关 

用户名和密码登录

短信登录

页面展示

提交数据

根据数据去数据库校验

通过,登录成功 session

失败,页面展示错误信

登录成功后,保存用户的信息session【文件、数据库、缓存中】

而今天最后的最后就是来写用户登录的代码

3.2.1 基本页面

我们先创建一个templates存放html,因为我们后面要写的视图函数比较多,所以我们就把views.py删掉换成 一个文件夹,文件夹里面放入给类视图函数,因为我们现在要做登录相关函数所以我们先创建一个account.py。

我们先写一个简单的登录页面,这里给大家介绍几个需要注意的点,第一个就是如果是post请求(ajax除外),就一定要加上这个csrf_token (不然就会报错),或者把中间件里的csrf关掉

{% csrf_token %}

3.2.2 bootstrap优化页面

但是我们发现这个样式有点丑,我们可以借用bootstrap进行美化,我们先去下载,然后在app目录下面创建static文件夹,然后再创建文件,把下载好的bootstrap引入进去,然后再引入到html里面

这样就是创建好了,我们就要开始写好看的页面了,这里不过多叙述,我们就借助ai和bootstrap来写一个稍微能看的页面就行了,这里直接给出代码

login.html

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static '/plugins/bootstrap/css/bootstrap.css' %}"> <style> .box { width: 400px; border: 1px solid #dddddd; margin-left: auto; margin-right: auto; margin-top: 200px; padding-left: 40px; padding-right: 40px; padding-bottom: 30px; box-shadow: 5px 10px 10px rgb(0 0 0 /5%); } </style> </head> <body> <div class="box"> <h2 style="text-align: center">用户登录</h2> <form method="post" action="{% url 'login' %}" class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label>角色</label> <select name="role" class="form-control"> <option value="2">客户</option> <option value="1">管理员</option> </select> </div> <div class="form-group"> <label>用户名</label> <input type="text" class="form-control" id="exampleInputPassword1" name="username" placeholder="用户名"> </div> <div class="form-group"> <label>密码</label> <input type="password" class="form-control" id="exampleInputPassword1" name="password" placeholder="密码"> </div> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> 记住我 </label> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">登录</button> <a href="{% url 'sms_login' %}" style="float: right">短信登录</a> </div> </form> </div> <h1></h1> </body> </html>

 sms_login.html

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static '/plugins/bootstrap/css/bootstrap.css' %}"> <style> .box { width: 400px; border: 1px solid #dddddd; margin-left: auto; margin-right: auto; margin-top: 200px; padding-left: 40px; padding-right: 40px; padding-bottom: 30px; box-shadow: 5px 10px 10px rgb(0 0 0 /5%); } </style> </head> <body> <div class="box"> <h2 style="text-align: center">短信登录</h2> <form method="post" action="{% url 'sms_login' %}" class="form-horizontal"> {% csrf_token %} <div class="form-group"> <label>角色</label> <select name="role" class="form-control"> <option value="2">客户</option> <option value="1">管理员</option> </select> </div> <div class="form-group"> <label>手机号</label> <input type="text" class="form-control" id="inputEmail3" name="mobile" placeholder="手机号"> </div> <div class="form-group"> <label>短信验证码</label> <div class="row"> <div></div> <div class="col-md-9"> <input type="password" class="form-control" id="inputPassword3" name="code" placeholder="短信验证码"> </div> <div class="col-md-3"> <input type="button" value="发送短信" class="btn btn-default"> </div> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">登录</button> <a href="{% url 'login' %}" style="float: right">用户名登录</a> </div> </form> </div> </body> </html>

 

优化后的页面虽然不好看,但是也能看 

3.2.3 登录逻辑

我们现在就要来写登录逻辑了,我们在写登录逻辑之前肯定知道,如果登录成功肯定要存储到session,我们上节说过session配置缓存,那我们就要按照上节配置一下。

我们创建数据库存放密码的时候,通常用的是密文,那我们就创建一个utils文件夹来存放加密解密

 

这个seetings.SECRET_KEY是django创建项目的时候生成的,我们把这个充当是盐

这样我们一个简单的登录逻辑就写好了,那我们就要开始测试了,我们平时都是直接在数据库里面写入数据,但是这个密码是md5还要加上盐,我们手动创建会很麻烦,我们可以写一点离线脚本

import os import sys import django base_dir=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(base_dir) os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings') django.setup() from web import models from utils.encrypt import md5 models.Administrator.objects.create(username='admin',password=md5('admin'),mobile='18888888888')

我们通过这个就能写django的离线脚本了,通过脚本给数据库添加点东西,或者还可以在项目终端执行python manage.py shell,然后写入代码,但是这个直接一次性执行,不是很方便

3.2.4 form表单验证

我们现在想想,我们这个是不是什么都不输入并且密码错误以后什么都没有了,此时我们就要借助form,这样可以解决这一点

from django.shortcuts import render,redirect from web import models from utils.encrypt import md5 from django import forms class LoginForm(forms.Form): role=forms.ChoiceField(required=True,choices=(('2','客户'),('1','管理员')),widget=forms.Select(attrs={'class':'form-control'})) username=forms.CharField(required=True,widget=forms.TextInput(attrs={'class':'form-control','placeholder':'用户名'})) password=forms.CharField(required=True,widget=forms.PasswordInput(attrs={'class':'form-control','placeholder':'密码'})) #密码默认不保留 如果想要保留就加上render_value=True def login(request): if request.method=="GET": form=LoginForm() return render(request,'login.html',{'form':form}) #通过form来校验 form=LoginForm(request.POST) if not form.is_valid():#这个来开始校验 return render(request,'login.html',{'form':form}) #这个相当于是把上一次的值写进去 # 验证成功之后 form.cleaned_data就是存放的所有数据 username=form.cleaned_data.get('username') password=form.cleaned_data.get('password') password=md5(password) #我们存放到数据库要用md5 role=form.cleaned_data.get('role') mapping={'1':'ADMIN','2':'CUSTOMER'} if role not in mapping: return render(request,'login.html',{'form':form,'error':'角色不存在'}) if role=='1': user_object=models.Administrator.objects.filter(active=1,username=username,password=password).first() #因为是逻辑删除,所以一定要加上active=1 else: user_object = models.Customer.objects.filter(active=1, username=username, password=password).first() # 校验失败 if not user_object: return render(request, 'login.html', {'form':form,'error': '用户名或密码错误'}) # 校验成功存到session+进入项目后台 request.session['user_info']={'role':mapping[role],'name':user_object.username,'id':user_object.id} return redirect('/home/')

我直接上代码,也都加上了注释,但是我在这里把重要的点加上来给你说,下重点

1.定义form必须要继承forms.Form

2.最开始定义例如:username=forms.CharField(),括号里面加上一些字段,如果要带上标签的类型的话要加上widget=forms.TextInput() 或者别的标签,根据标签来,要加上的类型例如class,就写上attrs={},这样子,其中name不用写,name就默认等于定义的名字,如username

3.form=LoginForm(request.POST) 这个就是把requests.POST的值放进去一个个比对

4.form.is_valid() 来判断是否比对成功

5.比对成功之后form.cleaned_data就是一个字典,里面存放所有要的数据

最新的login.html

{% load static %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="{% static '/plugins/bootstrap/css/bootstrap.css' %}"> <style> .box { width: 400px; border: 1px solid #dddddd; margin-left: auto; margin-right: auto; margin-top: 200px; padding-left: 40px; padding-right: 40px; padding-bottom: 30px; box-shadow: 5px 10px 10px rgb(0 0 0 /5%); } @keyframes fadeOut { 0% { opacity: 1; } 99% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } } </style> </head> <body> <div class="box"> <h2 style="text-align: center">用户登录</h2> <form method="post" action="{% url 'login' %}" class="form-horizontal" > {% csrf_token %} <div class="form-group"> <label>角色</label> {{ form.role }} </div> <div class="form-group"> <label>用户名</label> {{ form.username }} </div> <div class="form-group"> <label>密码</label> {{ form.password }} </div> <div class="form-group"> <div class="checkbox"> <label> <input type="checkbox"> 记住我 </label> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">登录</button> <span style="animation: fadeOut 2s forwards;color: red">{{ error }}</span> <a href="{% url 'sms_login' %}" style="float: right">短信登录</a> </div> </form> </div> </body> </html> 四.总结 

今天说的内容有点多,刚开始嘛都这,我也写了好久了,主要的还是form表单的验证,下一期将更加完善这个项目,会涉及ajax,短信登陆等等

五.补充 

下一期将和大家开始讲项目,期待大家的点赞关注加收藏 

 

 

 

标签:

Django项目之订单管理part1由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“Django项目之订单管理part1