-
代码发布整体工作流程
代码发布这个功能可以基于很多方式很多语言来实现
我们这里主要用的是python相关的知识点来完成的,大体逻辑流程都是大差不差的
额外补充:p2p(比特流技术),为了减轻服务器的压力,将所有人即是下载者又是资源的上传者
-
服务器表的增删改查
我们从头到位将增删改查自己实现了一遍,目的是为了搭建项目增删改查基本业务逻辑,方便后续其他表的操作
modelform使用
from django.forms import ModelForm class MyModelForm(ModelForm): class Meta: model = models.UserInfo fields = '__all__' # 前端展示用户表所有的字段 exclude = ['id'] # 排除id字段 不展示到前端 # 渲染标签 form_obj = MyModelForm() # 校验数据 form_obj = MyModelForm(data=request.POST) # 新增数据 form_obj.save() # 编辑数据 渲染标签 form_obj = MyModelForm(instance=edit_obj) # 修改数据库中的数据 form_obj = MyModelForm(instance=edit_obj,data=request.POST) form_obj.save()
针对数据的删除功能,一般情况下都需要有一个二次确认的操作
我们是直接利用BOM操作里面的confirm确认框实现的
你还可以借助于第三方插件效果更好一点比如sweetalert插件
-
项目表的增删改查
直接拷贝服务器表所有的代码,修改变量名即可
-
项目优化
将modelform单独放到一个文件夹中
然后再将各个模型表对应的modelform中相同的代码抽取出来形成基类
给项目表再增两个字段
线上项目地址、关联服务器
-
发布任务
由于发布任务是针对项目的,所以为了之后新增任务的时候不需要自己选择项目,所以我们将发布任务做成项目表中的一个字段,点击该字段跳转到该项目对应的所有发布纪录中,之后在该发布纪录页面上开设新增按钮,将当前项目的主键值传递到后端,这样的话新增任务就无需自己选择项目了
内容概要
-
发布任务单新增页面
-
发布流程前端实时展示
任务列表的展示为了更加人性化,可以增加当前项目的项目名和环境
# 先简单的手动处理数据 完成添加功能
from app01.myforms.base import BaseModelForm
from app01 import models
class TaskModelForm(BaseModelForm):
class Meta:
model = models.DeployTask
fields = '__all__'
# 项目唯一标识 应该是自动生成的无需用户填写
# 项目由于已经带了主键值了 所以也无需用户填写
# 创建的任务单默认状态就是待发布 所以也无需展示
exclude = ['uid','project','status']
def __init__(self,project_id,*args,**kwargs):
super().__init__(*args,**kwargs)
self.project_id = project_id
def save(self, commit=True):
# 添加数据
self.instance.uid = 'sadjasdj'
self.instance.project_id = self.project_id
# 调用父类的save方法保存数据
super().save(commit=True)
接下来,我们针对任务的添加页面,单独开设一个html
并在该html页面上划分三块区域展示不同的信息
-
当前任务关联的项目基本信息展示
-
基本配置
-
脚本书写
针对四个钩子脚本,我们想要实现可以保存的功能
# 初始化字段
def __init__(self,project_obj,*args,**kwargs):
super().__init__(*args,**kwargs)
self.project_obj = project_obj
# 初始化选择框内容
self.init_hook()
def init_hook(self):
# 给所有的下拉框先添加一个 请选择选项
# <option value="0">请选择</option> (0,'请选择')
self.fields['before_download_select'].choices = [(0,'请选择')]
self.fields['after_download_select'].choices = [(0,'请选择')]
self.fields['before_deploy_select'].choices = [(0,'请选择')]
self.fields['after_deploy_select'].choices = [(0,'请选择')]
创建表存储用户填写的想要保存的模版信息
class HookTemplate(models.Model):
"""保存钩子脚本"""
title = models.CharField(verbose_name='标题',max_length=32)
content = models.TextField(verbose_name='脚本内容')
# 我想实现钩子与钩子之间模版互不影响
hook_type_choices = (
(2,'下载前'),
(4,'下载后'),
(6,'发布前'),
(8,'发布后')
)
hook_type = models.IntegerField(verbose_name='钩子类型',choices=hook_type_choices)
什么时候需要操作上述表保存数据???
判断依据是用户是否点击了checkbox按钮
在我们重写的save方法中 做判断!!!
# 判断用户是否点击checkbox
if self.cleaned_data.get("before_download_template"):
# 获取模版名称
title = self.cleaned_data.get("before_download_title")
# 获取脚本内容
content = self.cleaned_data.get("before_download_script")
# 保存到表中
models.HookTemplate.objects.create(
title=title,
content=content,
hook_type=2
)
if self.cleaned_data.get("after_download_template"):
# 获取模版名称
title = self.cleaned_data.get("after_download_title")
# 获取脚本内容
content = self.cleaned_data.get("after_download_script")
# 保存到表中
models.HookTemplate.objects.create(
title=title,
content=content,
hook_type=4
)
if self.cleaned_data.get("before_deploy_template"):
# 获取模版名称
title = self.cleaned_data.get("before_deploy_title")
# 获取脚本内容
content = self.cleaned_data.get("before_deploy_script")
# 保存到表中
models.HookTemplate.objects.create(
title=title,
content=content,
hook_type=6
)
if self.cleaned_data.get("after_deploy_template"):
# 获取模版名称
title = self.cleaned_data.get("after_deploy_title")
# 获取脚本内容
content = self.cleaned_data.get("after_deploy_script")
# 保存到表中
models.HookTemplate.objects.create(
title=title,
content=content,
hook_type=8
)
数据库模版名称前端展示,以及用户点击实时获取模版内容
def init_hook(self):
# 给所有的下拉框先添加一个 请选择选项
# <option value="0">请选择</option> (0,'请选择')
before_download = [(0,'请选择')]
# 还应该去数据库中查询是否有对应的模版名称
extra_list = models.HookTemplate.objects.filter(hook_type=2).values_list('pk','title')
before_download.extend(extra_list)
self.fields['before_download_select'].choices = before_download
after_download = [(0,'请选择')]
extra_list = models.HookTemplate.objects.filter(hook_type=4).values_list('pk', 'title')
after_download.extend(extra_list)
self.fields['after_download_select'].choices = after_download
before_deploy = [(0,'请选择')]
extra_list = models.HookTemplate.objects.filter(hook_type=6).values_list('pk', 'title')
before_deploy.extend(extra_list)
self.fields['before_deploy_select'].choices = before_deploy
after_deploy = [(0,'请选择')]
extra_list = models.HookTemplate.objects.filter(hook_type=8).values_list('pk', 'title')
after_deploy.extend(extra_list)
self.fields['after_deploy_select'].choices = after_deploy
前端用户点击模版动态展示内容
给前端所有的select标签绑定文本域变化事件(change事件)
<script>
// 直接给hooks类标签内所有的select绑定事件
$('.hooks').find('select').change(function () {
{#alert($(this).val()) 获取用户输入的模版主键值 #}
var $that = $(this);
// 朝后端发送请求 获取对应的脚本内容
$.ajax({
url:'/hook/template/'+$that.val()+'/',
type:'get',
dataType:'JSON',
success:function (args) {
// 获取脚本内容 渲染到对应下拉框下面的textarea框中
{#alert(args.content)#}
// 标签查找
$that.parent().parent().next().find('textarea').val(args.content);
}
})
})
</script>
优化
-
用户一旦点击了checkbox按钮,那么就必须填写模版名称
# 钩子函数 全局钩子 局部钩子 # 全局钩子 # 全局钩子校验用户是否点击checkbox def clean(self): if self.cleaned_data.get('before_download_template'): # 获取用户输入的模版名称 判断是否有值 title = self.cleaned_data.get("before_download_title") if not title: # 展示提示信息 self.add_error('before_download_title','请输入模版名称') if self.cleaned_data.get('after_download_template'): # 获取用户输入的模版名称 判断是否有值 title = self.cleaned_data.get("after_download_title") if not title: # 展示提示信息 self.add_error('after_download_title','请输入模版名称') if self.cleaned_data.get('before_deploy_template'): # 获取用户输入的模版名称 判断是否有值 title = self.cleaned_data.get("before_deploy_title") if not title: # 展示提示信息 self.add_error('before_deploy_title','请输入模版名称') if self.cleaned_data.get('after_deploy_template'): # 获取用户输入的模版名称 判断是否有值 title = self.cleaned_data.get("after_deploy_title") if not title: # 展示提示信息 self.add_error('after_deploy_title','请输入模版名称')
注意,前端需要预留一部分内容展示错误信息否则会出现布局错乱的问题
<div class="form-group" style="height: 60px"> <div class="col-sm-3"> <div class="checkbox"> <label for="">{{ form_obj.after_deploy_template }}保存模版</label> </div> </div> <div class="col-sm-9"> {{ form_obj.after_deploy_title }} <span style="color: red">{{ form_obj.after_deploy_title.errors.0 }}</span> </div> </div>
发布任务
Ps:静态文件可以全局也可以在局部
静态文件配置
# 1 配置文件中直接配置 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static1'), os.path.join(BASE_DIR,'static2'), ] # 2 模版语法直接配置 {% load staticfiles %} <script src="{% static 'js/go.js' %}"></script>
<script> // 由于ws和diagram需要在其他函数内使用 所以定义成全局变量 var ws; var diagram; function initWebSocket() { ws = new WebSocket('ws://127.0.0.1:8000/publish/{{ task_obj.pk }}/'); // 一旦服务端有消息 会自动触发onmessage方法 ws.onmessage = function (args) { // args.data var res = JSON.parse(args.data); if (res.code==='init'){ // 操作gojs渲染图表 diagram.model = new go.TreeModel(res.data) } } } function initTable() { var $ = go.GraphObject.make; diagram = $(go.Diagram, "diagramDiv", { layout: $(go.TreeLayout, { angle: 0, nodeSpacing: 20, layerSpacing: 70 }) }); // 生成一个节点模版 diagram.nodeTemplate = $(go.Node, "Auto", $(go.Shape, { figure: "RoundedRectangle", fill: 'yellow', stroke: 'yellow' }, new go.Binding("figure", "figure"), new go.Binding("fill", "color"), new go.Binding("stroke", "color")), $(go.TextBlock, {margin: 8}, new go.Binding("text", "text")) ); // 生成一个箭头模版 diagram.linkTemplate = $(go.Link, {routing: go.Link.Orthogonal}, $(go.Shape, {stroke: 'yellow'}, new go.Binding('stroke', 'link_color')), $(go.Shape, {toArrow: "OpenTriangle", stroke: 'yellow'}, new go.Binding('stroke', 'link_color')) ); // 数据集合 以后替换ajax请求 注意使用key和parent来规定箭头的指向 {#var nodeDataArray = [#} {# {key: "start", text: '开始', figure: 'Ellipse', color: "lightgreen"},#} {# {key: "download", parent: 'start', text: '下载代码', color: "lightgreen", link_text: '执行中...'},#} {# {key: "compile", parent: 'download', text: '本地编译', color: "lightgreen"},#} {# {key: "zip", parent: 'compile', text: '打包', color: "red", link_color: 'red'},#} {# {key: "c1", text: '服务器1', parent: "zip"},#} {# {key: "c11", text: '服务重启', parent: "c1"},#} {# {key: "c2", text: '服务器2', parent: "zip"},#} {# {key: "c21", text: '服务重启', parent: "c2"},#} {# {key: "c3", text: '服务器3', parent: "zip"},#} {# {key: "c31", text: '服务重启', parent: "c3"},#} {#];#} {#diagram.model = new go.TreeModel(nodeDataArray);#} // 动态控制节点颜色变化 后端给一个key值 即可查找图表并修改 /* var node = diagram.model.findNodeDataForKey("zip"); diagram.model.setDataProperty(node, "color", "lightgreen"); } */ } // 页面加载完毕 先自动执行两个函数 给全局变量赋值 $(function () { initWebSocket(); initTable() }); function createDiagram() { ws.send('init') } </script>