20200319 代码发布之任务发布钩子脚本

昨日内容

* ### 代码发布整体工作流程

  参考qq截图

  代码发布这个功能可以基于很多方式很多语言来实现

  我们这里主要用的是python相关的知识点来完成的,大体逻辑流程都是大差不差的

  额外补充:p2p(比特流技术),为了减轻服务器的压力,将所有人即是下载者又是资源的上传者

* ### 服务器表的增删改查

  我们从头到位将增删改查自己实现了一遍,目的是为了搭建项目增删改查基本业务逻辑,方便后续其他表的操作

  #### **modelform使用**

  ```python
  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中相同的代码抽取出来形成基类

给项目表再增两个字段

线上项目地址、关联服务器

### 发布任务

由于发布任务是针对项目的,所以为了之后新增任务的时候不需要自己选择项目,所以我们将发布任务做成项目表中的一个字段,点击该字段跳转到该项目对应的所有发布纪录中,之后在该发布纪录页面上开设新增按钮,将当前项目的主键值传递到后端,这样的话新增任务就无需自己选择项目了

今日

  • 发布任务单新增页面
  • 发布流程前端实时展示

任务发布数据

任务列表的展示为了更加人性化,可以增加当前项目的项目名和环境

formmodel
    # 剔除参与展示的字段
    exclude = ['uid','project','status']
    
    # form_obj.instance  就是当前的数据对象
    form_obj.instance.uid = '唯一标识'
    
    
    
# tasklist有名分组需要传递参数,使用reverse反向解析
    url = reverse('task_list',args=(project_id,))
    return redirect(url)

    

初步进行封装重写modelform类的save方法,实现数据的添加

class TaskModelForm(BaseModelForm):
    class Meta:
        model = models.DeployTask
        fields = '__all__'

        # 剔除参与展示的字段
        exclude = ['uid','project','status']

    # 利用初始化获取project_id
    def __init__(self,project_id,*args,**kwargs):
        super().__init__(*args,**kwargs)
        self.project_id = project_id


    def save(self, commit=True):
        # 添加数据  (重写init方法进行数据的获取)
        # .instance 就是数据对象本身,重写save进行保存
        self.instance.uid = '唯一标识'
        self.instance.project_id = self.project_obj.pk
        # 调用父类save方法保存数据
        super().save(commit=True)

接下来,我们针对任务的添加页面,单独开设一个html (task_form.html)

并在该html页面上划分三块区域展示不同的信息

  • 当前任务关联的项目基本信息展示

  • 基本配置

  • 脚本书写

获取用户输入的数据cleaned_data

tag = self.cleaned_data.get('tag')

钩子脚本展示

img

img

针对四个钩子脚本,我们想要实现可以保存的功能

# 初始化字段
    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,'请选择')]

生成新字段

需要新添加字段

# 利用之前定义的基类中的`exclude_bootstrap=[]`来控制样式的添加
    # checkbox无需添加样式
    exclude_bootstrap = [
        'before_download_template',
        'after_download_template',
        'before_deploy_template',
        'after_deploy_template'

    ]
    
# 自己定义新的字段
    # 下拉框 checkbox 文本框
    before_download_select = forms.ChoiceField(required=False, label='下载前')
    before_download_title = forms.CharField(required=False, label='模板名称')
    before_download_template = forms.BooleanField(required=False, widget=forms.CheckboxInput, label='是否保存为模板')

    after_download_select = forms.ChoiceField(required=False, label='下载后')
    after_download_title = forms.CharField(required=False, label='模板名称')
    after_download_template = forms.BooleanField(required=False, widget=forms.CheckboxInput, label='是否保存为模板')

    before_deploy_select = forms.ChoiceField(required=False, label='发布前')
    before_deploy_title = forms.CharField(required=False, label='模板名称')
    before_deploy_template = forms.BooleanField(required=False, widget=forms.CheckboxInput, label='是否保存为模板')

    after_deploy_select = forms.ChoiceField(required=False, label='下载后')
    after_deploy_title = forms.CharField(required=False, label='模板名称')
    after_deploy_template = forms.BooleanField(required=False, widget=forms.CheckboxInput, label='是否保存为模板')

下拉框的展示

初始化选择框内容,是的前端进行展示下拉框内容 choices=

img

# 初始化字段
    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()方法中判断
    def save(self, commit=True):
        # 添加数据
        self.instance.uid = self.create_uid()
        self.instance.project_id = self.project_obj.pk
        # 调用父类的save方法保存数据
        super().save(commit=True)
        
    	# 判断用户是否点击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
            )

下拉框显示钩子模板数据

# 给所有的下拉框先添加一个 请选择选项
        # <option value="0">请选择</option>  (0,'请选择')

        before_download = [(0,'请选择')]
        # 还应该去数据库中查询是否有对应的模版名称
        extra_list = models.HookTemplate.objects.filter(hook_type=2).values_list('pk','title')
        # 利用extend扩展列表 (append只是添加)
        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

实时获取数据动态展示

给前端所有的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>
def hook_template(request,hook_id):
    # 根据hook_id查询出hook对象
    hook_obj = models.HookTemplate.objects.filter(pk=hook_id).first()
    back_dic = {'status':1000,'content':''}
    back_dic['content'] = hook_obj.content
    return JsonResponse(back_dic)

优化

用户一旦点击了checkbox按钮,那么就必须填写模版名称(进行校验)def 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-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>

新建发布任务接口

url(r'^deploy/(?P<task_id>d+)/$',deploy.deploy_task,name='deploy_task')


from django.shortcuts import HttpResponse,render,redirect,reverse
from app01 import models

def deploy_task(request,task_id):
    task_obj = models.DeployTask.objects.filter(pk=task_id).first()
    return render(request,'deploy.html',locals())

使用gojs展示流程图

<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>

利用channels实现群发的功能

  • 注册
INSTALLED_APPS = [
    'django.contrib.admin',
    ...
    'channels',
]
  • 配置
ASGI_APPLICATION = 'dm_fabu03.routing.application'
  • 新建routing
from channels.routing import ProtocolTypeRouter,URLRouter
from django.conf.urls import url
from app01 import consumers



"""consumers.py 当逻辑也非常多的时候 你也可以建成文件夹里面包含多个文件的形式"""
application = ProtocolTypeRouter({
    'websocket':URLRouter([
        url(r'^publish/(?P<task_id>d+)/$',consumers.PublishConsumer)
    ])
})

前端钩子脚本

task_form.html

{% extends 'base.html' %}



{% block css %}
    <style>
        .outline .series .module {
            line-height: 100px;
            vertical-align: middle;
             940px;
            margin: 0 auto;
            padding-bottom: 10px;
        }

        .outline .series .module .item .line {
            float: left;
             80px;

        }

        .outline .series .module .item .line hr {
            margin-top: 49px
        }

        .outline .series .module .item .icon {
            float: left;
            color: #dddddd;
            position: relative;

        }

        .outline .series .module .item .icon .up, .outline .series .module .item .icon .down {
            position: absolute;
            line-height: 49px;
            min- 90px;
            left: 0;
            text-align: center;
            margin-left: -38px;
            color: #337ab7;
        }

        .outline .series .module .item:hover .icon, .outline .series .module .item.active .icon {
            color: green;
        }

        .outline .series .module .item .icon .up {
            top: 0;
        }

        .outline .series .module .item .icon .down {
            bottom: 0;

        }
    </style>
{% endblock %}

{% block content %}
    {#    1 基本信息展示#}
    <table class="table table-hover table-striped table-bordered">
        <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>
    <form action="" method="post" novalidate>
        {% csrf_token %}
        {#    2 基本配置#}
        <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">
                <div class="form-horizontal">
                    <div class="form-group">
                        <label for="{{ form_obj.tag.id_for_label }}"
                               class="col-sm-2 control-label">{{ form_obj.tag.label }}</label>
                        <div class="col-sm-10">
                            {{ form_obj.tag }}
                            <span>{{ form_obj.tag.errors.0 }}</span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        {#    3 脚本钩子渲染#}
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title"><span class="glyphicon glyphicon-tasks"></span>发布流程&脚本</h3>
            </div>
            <div class="panel-body">
                {#            4 执行流程图即钩子脚本作用地展示#}
                <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>
                {#            5 四个脚本展示#}
                <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">
                                <div class="form-horizontal">
                                    <div class="form-group">
{#                                        下拉框#}
                                        <div class="col-sm-12">
                                            {{ form_obj.before_download_select }}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            {{ form_obj.before_download_script }}
                                        </div>
                                    </div>
                                    <div class="form-group" style="height: 60px">
                                        <div class="col-sm-3">
                                            <div class="checkbox">

                                                <label for="">{{ form_obj.before_download_template }}保存模版</label>
                                            </div>
                                        </div>
                                        <div class="col-sm-9">
                                            {{ form_obj.before_download_title }}
                                            <span style="color: red">{{ form_obj.before_download_title.errors.0 }}</span>
                                        </div>

                                    </div>
                                </div>
                            </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">
                                <div class="form-horizontal">
                                    <div class="form-group">
{#                                        下拉框#}
                                        <div class="col-sm-12">
                                            {{ form_obj.after_download_select }}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            {{ form_obj.after_download_script }}
                                        </div>
                                    </div>
                                    <div class="form-group" style="height: 60px">
                                        <div class="col-sm-3">
                                            <div class="checkbox">

                                                <label for="">{{ form_obj.after_download_template }}保存模版</label>
                                            </div>
                                        </div>
                                        <div class="col-sm-9">
                                            {{ form_obj.after_download_title }}
                                            <span style="color: red">{{ form_obj.after_download_title.errors.0 }}</span>
                                        </div>

                                    </div>
                                </div>
                            </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">
                                <div class="form-horizontal">
                                    <div class="form-group">
{#                                        下拉框#}
                                        <div class="col-sm-12">
                                            {{ form_obj.before_deploy_select }}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            {{ form_obj.before_deploy_script }}
                                        </div>
                                    </div>
                                    <div class="form-group" style="height: 60px">
                                        <div class="col-sm-3">
                                            <div class="checkbox">

                                                <label for="">{{ form_obj.before_deploy_template }}保存模版</label>
                                            </div>
                                        </div>
                                        <div class="col-sm-9">
                                            {{ form_obj.before_deploy_title }}
                                            <span style="color: red">{{ form_obj.before_deploy_title.errors.0 }}</span>
                                        </div>

                                    </div>
                                </div>
                            </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">
                                <div class="form-horizontal">
                                    <div class="form-group">
{#                                        下拉框#}
                                        <div class="col-sm-12">
                                            {{ form_obj.after_deploy_select }}
                                        </div>
                                    </div>
                                    <div class="form-group">
                                        <div class="col-sm-12">
                                            {{ form_obj.after_deploy_script }}
                                        </div>
                                    </div>
                                    <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>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <input type="submit" class="btn btn-success">
    </form>

{% endblock %}


{% block js %}
    <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>
{% endblock %}
原文地址:https://www.cnblogs.com/fwzzz/p/12733993.html