轻量级bug管理平台——管理中心
管理中心预计效果如下:

django写离线脚本
探讨业务
设计表结构
我的表结构
功能实现【任务】
- 查看项目列表
- 创建项目
- 星标项目
今日详细
1、django 离线脚本
- django ,框架
- 离线,非web运行时(运行时与django运行与否没有关系)
- 脚本,一个或者几个py文件
- 了解以上知识点后在某个py文件中对django项目做一些处理。
web运行时的含义:运行django程序运行起来就叫django运行时,即Web网站运行起来,对于视图文件的视图函数是通过浏览器中访问网站,django网站自动触发函数,函数被称为web在运行时被django调用的函数。
示例1:使用离线脚本在用户表插入数据(仅运行i脚本文件)
# 仅运行脚本文件时要特地往数据库添加数据:链接数据库、操作、关闭链接
#而在django启动时,实际上是执行manage.py文件,在运行是会把所有的配置文件加载在项目中,。这时候就可以链接数据库并运行了,但离线脚本是不行的。
在django中写离线脚本的程序如下:
在init_user中:
import os
import sys
import django
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 绝对路径,不断地往上一层找,从而找到它的根目录.
sys.path.append(base_dir) # 将根目录写在sys.path下,在内部就导入s25下面的那个settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"bugmanagment.settings") # 将bugmanagment.settings写入DJANGO_SETTINGS_MODULE环境变量中
django.setup() # os.environ['DJANGO_SETTINGS_MODULE'] #模拟django的manage.py文件,进行模拟运行django启动,从而读到配置文件
from app01 import models
# 仅运行脚本文件时:往数据库添加数据:链接数据库、操作、关闭链接
models.UserInfo.objects.create(username='陈硕', email='chengshuo@live.com', mobile_phone='13838383838', password='123123')
运行结果如下:(加入数据库里面的结果:

示例2:数据库录入全国省市县
示例3:朋友圈项目敏感字、词语
示例4:saas免费版(为所有人提供项目管理平台)【价格策略通过离线脚本添加进去)
注:大量数据要插入数据库中时可以利用一下django的离线脚本进行操作。
2. 探究业务( 表设计)



注:request.tracer = 交易对象




3
1、创建相应表结构:
知识点扩展:

加入related_name解决一张表关联着同一张表的ForeignKey的问题

class ProjectUser(models.Model): # 项目参与者 user = models.ForeignKey(verbose_name='参与者', to='UserInfo', related_name='a', on_delete=models.CASCADE) project = models.ForeignKey(verbose_name='项目', to='Project', on_delete=models.CASCADE) star = models.BooleanField(verbose_name='星标', default=False) invitee = models.ForeignKey(verbose_name='邀请者', to='UserInfo', related_name='b', on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name='加入时间', auto_now_add=True) obj = UserInfo.objects.filter(id=1) # 用户对象 # obj.projectuser_set.all() #通过字段反向关联ProjectUser表,但是因为一张表关联着同一张的ForeignKey,则应该用下一个操作才能实现 obj.a.all() #或者obj.b.all()
代码:
from django.db import models class UserInfo(models.Model): username = models.CharField(verbose_name='用户名', max_length=32, db_index=True) # db_index=True 创建索引 email = models.EmailField(verbose_name='邮箱', max_length=32) mobile_phone = models.CharField(verbose_name='手机号', max_length=32) password = models.CharField(verbose_name='密码', max_length=32) # price_policy = models.ForeignKey(verbose_name="价格策略", to='PricePolicy', null=True, blank=True) def __str__(self): return self.username class Meta: db_table = "UserInfo" verbose_name_plural = '用户表' class PricePolicy(models.Model): """价格策略""" category_choices = ( (1, '免费版'), (2, '收费版'), (3, '其他'), ) category = models.SmallIntegerField(verbose_name='收费类型', default=1, choices=category_choices) title = models.CharField(verbose_name='标题', max_length=32) price = models.PositiveIntegerField(verbose_name='价格') # 存正整数 project_num = models.PositiveIntegerField(verbose_name='项目数') project_member = models.PositiveIntegerField(verbose_name='项目成员数') project_space = models.PositiveIntegerField(verbose_name='单项目空间') per_file_size = models.PositiveIntegerField(verbose_name='单文件大小') create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) class Transaction(models.Model): # 交易记录 status_choice = ( (1, '未支付'), (2, '已支付') ) status = models.SmallIntegerField(verbose_name='状态', choices=status_choice) order = models.CharField(verbose_name='订单号', max_length=64, unique=True) # 唯一索引 user = models.ForeignKey(verbose_name='用户', to='UserInfo', on_delete=models.CASCADE) price_policy = models.ForeignKey(verbose_name='价格策略', to='PricePolicy', on_delete=models.CASCADE) count = models.IntegerField(verbose_name='数量(年)', help_text='0表示无限期') price = models.IntegerField(verbose_name='实际支付价格') start_datetime = models.DateTimeField(verbose_name='开始时间', null=True, blank=True) end_datetime = models.DateTimeField(verbose_name='结束时间', null=True, blank=True) # 为空代表无限期 create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) class Project(models.Model): """项目表""" COLOR_CHOICES = ( (1, "#56b8eb"), (2, "#f28033"), (3, "#ebc656"), (4, "#a2d148"), (5, "#20BFA4"), (6, "#7461c2"), (7, "#20bfa3"), ) name = models.CharField(verbose_name='项目名', max_length=32) color = models.SmallIntegerField(verbose_name='颜色', choices=COLOR_CHOICES, default=1) desc = models.CharField(verbose_name='项目描述', max_length=255, null=True, blank=True) use_space = models.IntegerField(verbose_name='项目已使用空间', default=0) star = models.BooleanField(verbose_name='星标', default=False) join_count = models.SmallIntegerField(verbose_name='参与人数', default=1) creator = models.ForeignKey(verbose_name='创建者', to='UserInfo', on_delete=models.CASCADE) create_datetime = models.DateTimeField(verbose_name='创建时间', auto_now_add=True) # 查询:可以省事;(根据project_user字段进行查询) # 增加、删除、修改、无法完成 project_user = models.ManyToManyField(to='UserInfo', through="ProjectUser", through_fields=('project', 'user')) # 定义多对多字段 class ProjectUser(models.Model): # 项目参与者 project = models.ForeignKey(verbose_name='项目', to='Project', on_delete=models.CASCADE) user = models.ForeignKey(verbose_name='参与者', to='UserInfo', on_delete=models.CASCADE) star = models.BooleanField(verbose_name='星标', default=False) create_datetime = models.DateTimeField(verbose_name='加入时间', auto_now_add=True)
2、离线脚本
scripts.py专门放脚本文件。
在base.py中:
# 离线脚本的模版 import django import os import sys base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(base_dir) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "bugmanagment.settings") django.setup() # os.environ['DJANGO_SETTINGS_MODULE']
在init_price_policy中:
# 初始化价格策略 import base # 调用base from app01 import models def run(): exists = models.PricePolicy.objects.filter(category=1, title="个人免费版").exists() if not exists: models.PricePolicy.objects.create( category=1, title="个人免费版", price=0, project_num=3, project_member=2, project_space=20, per_file_size=5 ) if __name__ == '__main__': run()
3、用户注册【对前面的代码进行修改】
- 以前:创建用户
- 现在:用户&交易记录(获得免费版的额度)
def register(request): # 注册 if request.method == 'GET': form = RegisterModelForm() return render(request, 'web/register.html', {'form': form}) # print(request.POST) # 后台拿到的数据,传到ModelForm进行校验 form = RegisterModelForm(data=request.POST) if form.is_valid(): # 验证通过后,写入数据库(密码要是密文) # instance = form.save,在数据库中新增一条数据,并将新增的这条数据赋值给instance,指的是当前我们添加的这条数据的用户对象 # 相当于models.UserInfo.objects.filter(id=1).first() # 用户表中新建一条数据(注册) # form.instance.password ="iudasndfiajsd;fj" #在保存之前将instance中的password进行重置 instance = form.save() # 自动剔除数据库中没有的字段 # 创建交易记录 policy_object = models.PricePolicy.objects.filter(category=1, title="个人免费版").first() #通过离线脚本添加的数据,取到价格策略的对象 models.Transaction.objects.create( status=2, order=str(uuid.uuid4()), user=instance, price_policy=policy_object, count=0, price=0, start_datetime=datetime.datetime.now() #开始时间为当前时间 ) return JsonResponse({'status': True, 'data': '/web/login/'}) return JsonResponse({'status': False, 'error': form.errors})
4、添加项目
4.1 项目列表母版+样式
- 后台:登录成功之后才可以访问
- 官网:都可以访问
- 通过中间件+白名单 对后台管理的权限进行处理
- 当前的拥有的价格策略【额度】

模态对话框要用ajax请求,而不能用form表单提交。
所有代码:
在母版manage.html中:
{#管理中心模版#}
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'plugin/bootstrap/css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'plugin/font-awesome/css/font-awesome.min.css' %}">
<style>
.navbar-inverse {
border-radius: 0;
}
.error-msg {
color: red;
position: absolute;
font-size: 13px;
}
</style>
{% block css %} {% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse ">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{% url 'project_list' %}">Tracer平台</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="dropdown active">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">项目 <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="#">Action</a></li>
<li><a href="#">Another action</a></li>
<li><a href="#">Something else here</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">Separated link</a></li>
<li role="separator" class="divider"></li>
<li><a href="#">One more separated link</a></li>
</ul>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">工作台</a></li>
<li><a href="#">日历</a></li>
<li><a href="#"> <i class="fa fa-bell-o" aria-hidden="true"></i></a></li>
<li><a href="#"><i class="fa fa-bookmark" aria-hidden="true"></i></a></li>
{% if request.tracer %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"
aria-expanded="false">{{ request.tracer.user.username }} <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="{% url 'index' %}">官网</a></li>
<li role="separator" class="divider"></li>
<li><a href="{% url 'logout' %}"> 退 出</a></li>
</ul>
</li>
{% else %}
<li><a href="{% url 'login' %}">登 录</a></li>
<li><a href="{% url 'register' %}">注 册</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
{% block content %} {% endblock %}
<script src="{% static 'js/jquery-3.4.1.min.js' %}"></script>
<script src="{% static 'plugin/bootstrap/js/bootstrap.min.js' %}"></script>
{% block js %} {% endblock %}
</body>
</html>
在project_list中:
{% extends 'layout/manage.html' %}
{% block css %}
<style>
.project {
margin-top: 10px;
}</style>
{% endblock %}
{% block content %}
<div class="container-fluid project">
<a class="btn btn-primary" data-toggle="modal" data-target="#addModal">
<i class="fa fa-plus-circle" aria-hidden="true"></i>
新建项目</a>
</div>
<!-- Modal -->
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel"> 新建项目</h4>
</div>
<div class="modal-body">
<form id="addForm">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
<span class="error-msg"></span>
</div>
{% endfor %}
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取 消</button>
<button id="btnSubmit" type="button" class="btn btn-primary">确 定</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
$(function () {
{#console.log("加載頁面");#}
bindSubmit();
});
function bindSubmit() {
console.log("加載頁面SSSS");
$('#btnSubmit').click(function () {
{#console.log("my");#}
{#向后台发送ajax请求.#}
$.ajax({
url: "{% url 'project_list' %}",
{#POST请求代表添加项目,GET请求代表展示项目#}
type: "POST",
{#获取表单中的数据#}
data: $('#addForm').serialize(),
dataType: "JSON",
success: function (res) {
if (res.status) {
console.log(res);
{#让页面主动刷新#}
{#location.href = location.href;#}
location.reload()
} else {
//错误信息
//返回的res是字典:{status:False, error:{mobile_phone:["错误信息",],code:["错误信息”,]}}
$.each(res.error, function (key, value) {
$("#id_" + key).next().text(value[0]);
})
}
}
})
})
}
</script>
{% endblock %}
在views\project.py中:
from django.shortcuts import render from app01 import models from web.forms.project import ProjectModelForm from django.http import JsonResponse def project_list(request): """项目列表""" # print(request.transaction.user) # print(request.transaction.price_policy.project_num) if request.method == "GET": return render(request, 'web/project_list.html', {'form': form}) # POST.对话框的ajax添加项目. form = ProjectModelForm(request, data=request.POST) # 接收到请求(前端是用POST请求),进行表单验证 if form.is_valid(): # 验证通过:项目名、颜色、描述+creator谁创建的项目? 其他的内容,项目表中已经有默认值了 form.instance.creator = request.tracer.user # 当前登录的用户对象 # 创建项目 form.save() # 在保存之前要让models.py项目表中的字段都有值 return JsonResponse({'status': True}) return JsonResponse({'status': False, 'error': form.errors}) # 校验失败,返回错误信息
在forms\project.py中
from django import forms from app01 import models from web.forms.bootstrap import BootStrapForm from django.core.exceptions import ValidationError class ProjectModelForm(BootStrapForm, forms.ModelForm): # desc = forms.CharField(widget=forms.Textarea()) class Meta: model = models.Project fields = ['name', 'color', 'desc'] widgets = { 'desc': forms.Textarea } #ModelForm里没有request,要重写init方法,在接口传一个request进来 def __init__(self, request, *args, **kwargs): super().__init__(*args, **kwargs) self.request = request def clean_name(self): """项目校验""" name = self.cleaned_data['name'] # 1.当前用户(self.request.tracer.user)是否已创建过此类项目(项目名不能重复)? # 1.1 所有人是否已创建过这个项目 # exists = models.Project.objects.filter(name=name).exists() # self.request.tracer.user exists = models.Project.objects.filter(name=name, creator=self.request.tracer.user).exists() if exists: raise ValidationError("项目名已存在") # 2.当前用户是否还有额度进行创建项目? # 最多创建N个项目 # 项目额度(self.request.tracer.price_policy.project_num) # 现在已创建多少项目? count = models.Project.objects.filter(creator=self.request.tracer.user).count() if count >=self.request.tracer.price_policy.project_num: raise ValidationError('项目个数超限,请购买套餐') return name

内容概要:
- 展示项目
- 星标项目
- 添加项目:颜色选择
- 项目切换&颜色选择
- 项目切换&项目管理菜单处置
- wiki管理
1、展示项目:

1.1数据
1、从数据库中获取两部分数据
我创建的所有项目:含有已星标,未星标项目
我参与的所有项目:含有已星标,未星标项目
2、提取已星标项目
列表 = 循环 【我创建的所有项目】+【我参与的所有项目】把已星标的数据提取
3、页面渲染循环三组数据进行页面展示。
得到三个列表 : 星标、创建、参与
2、星标项目(去除星标)
2.1星标
我创建的项目:表中Project的star=True
我参与的项目:表中ProjectUser的star=True
2.2移除星标
我创建的项目:表中Project的star=False
我参与的项目:表中ProjectUser的star=False
3.选择颜色
颜色的选择将表中choice默认生成的select标签改成radio标签.
3.1、部分样式应用BootStrap
# 面向对象的继承,在form和ModelForm中进行样式的设置 class BootStrapForm(object): bootstrap_class_exclude = [] # 支持某个字段不加样式 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for name, field in self.fields.items(): # 循环字段进行样式的设置 if name in self.bootstrap_class_exclude: # 不运用bootstrap样式 continue field.widget.attrs['class'] = 'form-control' field.widget.attrs['placeholder'] = '请输入%s' % (field.label,)
class ProjectModelForm(BootStrapForm, forms.ModelForm): bootstrap_class_exclude = ['color'] # 走BootStrapForm中的init方法,把颜色的样式排除掉,此为不走BootStrapForm样式的方法
class Meta: model = models.Project fields ="_all_"
3.2定制ModelForm的插件(不用django默认提供的插件,不来自django的forms中)
class ProjectModelForm(BootStrapForm, forms.ModelForm): class Meta: model = models.Project fields = "_all_" widgets = { 'desc': forms.Textarea, 'color': ColorRadioSelect(attrs={'class': 'color-radio'}) }
注:'desc'利用的是默认的插件,color用的是自定义的插件。
from django.forms import RadioSelect class ColorRadioSelect(RadioSelect): template_name = 'widgets/color_radio/radio.html' option_template_name = 'widgets/color_radio/radio_option.html'
自定义样式:
在radio.html中:
{% with id=widget.attrs.id %}
<div {% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }}"{% endif %}>
{% for group,options,index in widget.optgroups %}
{% for option in options %}
<label {% if option.attrs.id %} for="{{ option.attrs.id }}"{% endif %}>
{% include option.template_name with widget=option %}
</label>
{% endfor %}
{% endfor %}
</div>
{% endwith %}
在radio_option.html中:
{% include 'django/forms/widgets/input.html' %}
<span class="cycle" style="background-color: {{ option.label }}"></span>
3.3项目选择颜色
- 就是3.1、3.2的关于知识点的应用+前端样式的编写
4、切换菜单
1.数据库中获取 我创建的: 我参与的: 2.循环显示 3.当前页面需要显示/其他页面也需要显示【模版中自定义的方法——inclusion_tag】
代码如下 :
在templatetags\project.py中:
from django.template import Library from app01 import models register = Library() @register.inclusion_tag('inclusion/all_project_list.html') def all_project_list(request): # 1.获取我创建的所有项目 my_project_list = models.Project.objects.filter(creator=request.tracer.user) # 2.获取我参与的所有项目 join_project_list = models.ProjectUser.objects.filter(user=request.tracer.user) # return {'my': [], 'join': []} #返回一个这样的字典. return {'my': my_project_list, 'join': join_project_list} # 运用all_project_list这个inclusion_tag后,返回模版
在all_project_list.html中
<ul class="nav navbar-nav"> <li class="dropdown active"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">项目 <span class="caret"></span></a> <ul class="dropdown-menu"> {# my有值才展示#} {% if my %} <li><i class="fa fa-list" aria-hidden="true"></i>我创建的项目</li> {% for item in my %} <li><a href="#">{{ item.name }}</a></li> {% endfor %} <li role="separator" class="divider"></li> {% endif %} {% if join %} <li><i class="fa fa-handshake-o" aria-hidden="true"></i>我参与的项目</li> {% for item in join %} <li><a href="#">{{ item.project.name }}</a></li> {% endfor %} <li role="separator" class="divider"></li> {% endif %} <li><a href="{% url 'project_list' %}">所有项目</a></li> </ul> </li> </ul>
在母版manage.html中:插入两行代码即可
{% all_project_list request %}
{#调用inclusion_tag的时候记得要传入参数request#}
5.项目管理
写上它的多个功能以及他的路由url:
/manage/项目ID/dashboard /manage/项目ID/issues /manage/项目ID/statistics /manage/项目ID/file /manage/项目ID/wiki /manage/项目ID/setting
5.1 进入项目展示菜单
- 进入项目
- 展示菜单
5.1.1 是否进入项目?【访问url前通过访问中间件进行判断】
- 判断URL是否以manage开头 [以它开头的就是进入了]
- project_id是我创建or我参与的 (否则进入的很可能是别人的项目或者进不去)

5.1.2 显示菜单
依赖:是否已经进入项目?(即request.tracer.project有值表明进入了项目)
<ul class="nav navbar-nav"> <li><a href="#">产品功能</a></li> <li><a href="#">企业方案</a></li> <li><a href="#">帮助文档</a></li> <li><a href="#">价格</a></li> </ul>
5.1.3 修复bug
5.1.4 默认选中菜单
总结
- 项目实现思路
- 星标/取消星标
- inclusion_tag实现项目切换
- 项目菜单
- 中间件 process_view
- 点击后默认选中菜单:基于inclusion_tag选中
- 路由分发
- include(”xxx.url")
- include([ssfdsds,sdf]) #列表,找对应关系
- 颜色选择:源码+扩展【实现】 (了解即可) 其他的方法例如 :通过下拉框,或者通通过页面自己循环写input框
浙公网安备 33010602011771号