class DeployTask(models.Model): """发布任务单 项目主键 版本号 1 v1 1 v2 1 v3 2 v1 2 v2 """ # uid = luffycity-test-v1-20202020111 项目-环境-版本-日期 uid = models.CharField(verbose_name='唯一标识',max_length=128) project = models.ForeignKey(verbose_name='项目',to='Project') tag = models.CharField(verbose_name='版本',max_length=32) status_choices = ( (1,'待发布'), (2,'发布中'), (3,'成功'), (4,'失败'), ) status = models.IntegerField(verbose_name='状态',choices=status_choices,default=1) """我们在发布任务的时候 我开设了几个钩子脚本节点 可以支持发布者在发布流程的某个阶段执行额外的操作""" # 钩子节点的个数是不一定的 结合自己的公司需求 before_download_script = models.TextField(verbose_name='下载前脚本', null=True, blank=True) after_download_script = models.TextField(verbose_name='下载后脚本', null=True, blank=True) before_deploy_script = models.TextField(verbose_name='发布前脚本', null=True, blank=True) after_deploy_script = models.TextField(verbose_name='发布后脚本', null=True, blank=True)
而是在项目的查看列表中新增一个发布记录字典,点击该字段查看当前项目所对应的发布记录,然后在查看页面书写增删改查,这样做的目的就是可以直接针对对应的项目,无需用户自己选择
对于发布任务单的添加页面,不再使用公共的form.html而是自己单独开设一个,因为需要做额外的扩展
出于用户体验的角度考虑,我们将添加任务单的界面大致分为三块区域
-
基本信息展示区
<table class="table table-hover table-striped"> <tbody> <tr> <td>项目名称:{{ project_obj.title }}</td> <td>环境:{{ project_obj.get_env_display }}</td> </tr> <tr> <td colspan="2">仓库地址:{{ project_obj.repo }}</td> </tr> <tr> <td colspan="2">线上路径:{{ project_obj.path }}</td> </tr> <tr> <td colspan="2"> <div>关联服务器</div> <ul> {% for server_obj in project_obj.servers.all %} <li>{{ server_obj.hostname }}</li> {% endfor %} </ul> </td> </tr> </tbody> </table>
- 基本配置
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><span class="glyphicon glyphicon-cog"></span>基本配置</h3> </div> <div class="panel-body form-horizontal"> <label for="{{ form_obj.tag.id_for_label }}" class="col-md-2 control-label">{{ form_obj.tag.label }}</label> <div class="col-md-10"> {{ form_obj.tag }} <span style="color: red">{{ form_obj.tag.errors.0 }}</span> </div> </div> </div>
<div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title"><span class="glyphicon glyphicon-th-list"></span>发布流程&钩子</h3> </div> <div class="panel-body form-horizontal"> {# 1 简易流程图区域#} <div class="outline"> <div class="series"> <div class="module clearfix"> <div class="item left"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="down">01 开始</a> </div> </div> <div class="item left active"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="up">02 下载前</a> </div> </div> <div class="item left"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="down">03 下载代码</a> </div> </div> <div class="item left active"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="up">04 下载后</a> </div> </div> <div class="item left"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="down">05 打包上传</a> </div> </div> <div class="item left active"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="up">06 发布前</a> </div> </div> <div class="item left"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="down">07 发布</a> </div> </div> <div class="item left active"> <div class="line"> <hr> </div> <div class="icon"> <span class="glyphicon glyphicon-record" aria-hidden="true"></span> <a class="up">08 发布后</a> </div> </div> <div class="item left"> <div class="line"> <hr> </div> </div> </div> </div> </div> {# 2 钩子脚本渲染区域#} <div class="hooks"> <div class="col-md-6"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">02 下载前</h3> </div> <div class="panel-body"> {{ form_obj.before_download_script }} </div> </div> </div> <div class="col-md-6"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">04 下载后</h3> </div> <div class="panel-body"> {{ form_obj.after_download_script }} </div> </div> </div> <div class="col-md-6"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">06 发布前</h3> </div> <div class="panel-body"> {{ form_obj.before_deploy_script }} </div> </div> </div> <div class="col-md-6"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">08 发布后</h3> </div> <div class="panel-body"> {{ form_obj.after_deploy_script }} </div> </div> </div> </div> </div> </div>
def create_uid(self): # 生成唯一标识 # luffycity-test-v1-20202020111 如果有项目对象会非常简单 title = self.project_obj.title env = self.project_obj.env tag = self.cleaned_data.get('tag') current_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') return "{0}-{1}-{2}-{3}".format(title,env,tag,current_time)
实现用户定义的钩子脚本可以保存并且多次使用
这样做的好处在于,你这个软件使用的时间越久,内部保存的脚本越多,后续使用人员就可以方便的调用和修改之前的脚本内容从而减少工作量
我们前面写的taskmodelform中,没有下拉框,checkbox和模版文本框
我们需要去taskmodelform中额外的定义这些字段
<div class="panel-body form-horizontal"> <div class="form-group"> <div class="col-md-12"> {{ form_obj.after_download_select }} </div> </div> <div class="form-group"> <div class="col-md-12"> {{ form_obj.after_download_script }} <span style="color: red">{{ form_obj.after_download_script.errors.0 }}</span> </div> </div> <div class="form-group"> <div class="col-md-3"> <div class="checkbox"> <label>{{ form_obj.after_download_template }}保存为模版</label> </div> </div> <div class="col-md-9"> {{ form_obj.after_download_title }} <span style="color: red">{{ form_obj.after_download_title.errors.0 }}</span> </div> </div> </div>
def __init__(self,project_obj,*args,**kwargs): """当你不知道一个函数或者方法都有哪些参数的时候 就用*args,**kwargs哥俩""" super().__init__(*args,**kwargs) # 给对象添加额外的属性 self.project_obj = project_obj # 给下拉框添加数据 <option value="0">请选择</option> .choices使该字段前端变为下拉框 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=64) content = models.TextField(verbose_name='脚本内容') # 针对不同的脚本节点做对应的钩子标记 hook_type_choices = ( (2,'下载前'), (4,'下载后'), (6,'发布前'), (8,'发布后'), ) hook_type = models.IntegerField(verbose_name='钩子类型',choices=hook_type_choices)
当用户点击了checkbox按钮的时候,就应该去操作上面的表来存储脚本内容
我们在后端只需要判断用户是否点击了,再做相应的处理即可!!!
# 判断用户是否点击了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 )
# 给下拉框添加数据 <option value="0">请选择</option> before_download = [(0, '请选择')] extra_choices = models.HookTemplate.objects.filter(hook_type=2).values_list('pk','title') # [(),()] before_download.extend(extra_choices) self.fields['before_download_select'].choices = before_download
# 文本域改变事件 change事件 <script> // 给所有的下拉框绑定文本域改变事件 // 避免麻烦 直接使用范围查找 $('.hooks').find('select').change(function () { var that = $(this); // 发送ajax请求获取脚本内容 $.ajax({ url:'/hook/template/' + that.val() + '/', type:'get', dataType:'JSON', success:function (args) { if(args.status){ {#alert(args.content)#} // 将脚本内容通过DOM操作渲染到对应的文本框中 that.parent().parent().next().find('textarea').val(args.content) } } }) }) </script>
当用户点击了保存模版,但是没有填写脚本内容的时候,应该作出相应的提示信息
重写clean方法(局部钩子 全局钩子)
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-md-3"> <div class="checkbox"> <label>{{ form_obj.after_deploy_template }}保存为模版</label> </div> </div> <div class="col-md-9"> {{ form_obj.after_deploy_title }} <span style="color: red">{{ form_obj.after_deploy_title.errors.0 }}</span> </div> </div>
ModelForm的exclude
from django.forms import ModelForm class TaskModeForm(ModelForm): ... class Meta: model = models.DeployTask fields = '__all__' # 指定字段不展示到前端页面 exclude = ['uid','project','status'] def save(self, commit=True): self.instance.uid = self.create_uid() # .instance就是当前任务model的对象,不加为ModelForm对象|补上project_id,uid属性 self.instance.project_id = self.project_obj.pk # 调用父类的save方法保存数据 super().save(commit) # 判断用户是否点击了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 ) 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','请输入模板名称')