Django实现自动发布(2视图-服务管理)

通常页面要能对资源进行增删改查,对应http的 POST、DELETE、UPDATE、GET
页面显示使用了layui,而layui的表格有自己的数据获取方式,所以我们的视图要做一些调整,不使用后端渲染,只返回数据
具体的实现是页面点击按钮或者导航后,服务端渲染一个空的页面,剩下的数据由页面的js驱动获取。
返回空页面的视图:

from django.views import generic

class ServicePageView(generic.ListView):
    template_name = 'microservice/service.html'

    def get_queryset(self):
        pass

对应的urls:

url(r'^home/$', views.ServicePageView.as_view()),

空页面模板,这里的空指的是内容为空,模板还是有js的,
templates/microservice/service.html:

<table class="layui-hide" id="service-list-table" lay-filter="tableEvent"></table>
<script src="/static/js/layui/layui.js"></script>
<link rel="stylesheet" type="text/css" href="/static/js/layui/css/layui.css" />
  <script>
    layui.use(['laypage', 'layer', 'form', 'table'], function () {
      var laypage = layui.laypage //分页
        , layer = layui.layer //弹层
        , table = layui.table 
      ;
      //执行一个 table 实例
      table.render({
        elem: '#service-list-table'
        , url: '{% url "api_microservice" %}' //数据接口
        , cellMinWidth: 80 //全局定义常规单元格的最小宽度,layui 2.2.1 新增
        ,autoSort: false //禁用前端自动排序
        , page: {
          curr: 1
          , limit: 20
          , limits: [20, 30, 40, 50, 100]
          , layout: ['count', 'prev', 'page', 'next', 'limit', 'skip']
          , jump: function (obj) {
            console.log(obj)
          }
        }
        , cols: [[ //表头
          {field: 'name', title: 'service名称', sort: true, minWidth: 120}
          , {field: 'language', title: '语言类型', width: 100}
          , {field: 'description', title: '描述', minWidth: 150}
        ]]
        , id: 'serviceListTable'
      });

      /*
       * 表格搜索与重载
       */
      var $ = layui.$, active = {
        reload: handleReloadTable
      };
      // 重载
      function handleReloadTable(){
          $.ajax({
            url: '{% url "api_microservice" %}',
            type: "GET",
            dataType: "json",
          }).done(result => {
            table.reload('serviceListTable', {
              page: {
                curr: 1 //重新从第 1 页开始
              }
            });
          }).fail((xhr, status, error) => {
            
          })
      }
  </script>

服务管理视图

RESTful风格的api通常设计如下:

GET api/resources 返回数据列表
POST api/resources 则是创建一条记录
UPDATE api/resources/{id} 更新一条记录
DELETE api/resources/{id} 删除一条记录

将获取、创建的方法放在一个视图,修改、删除是另外一个视图

查找

from django.http import JsonResponse
from django.core.paginator import Paginator
from django.utils.timezone import utc, localtime
from microservice.models import *

class ServiceApi(generic.View):
    """
    服务管理操作 获取列表、添加
    """
    def get(self, request):
        query = request.GET
        page = query.get('page', 1)
        limit = query.get('limit', 20)

        services = Service.objects.select_related('updated_by')
        paginator = Paginator(services, limit)
        pdata = paginator.page(page)

        data = [{
            'id': item.id,
            'name': item.name,
            'description': item.description,
            'language': item.language,
            'build_orig': item.build_orig,
            'build_url': item.build_url,
            'updated_by': item.updated_by.username,
            'updated': localtime(item.updated).strftime('%Y-%m-%d %H:%M:%S %Z'),
        } for item in pdata]

        return JsonResponse({
            'data': data,
            'count': services.count(),
            'code': 0
        })

对应的urls:

url(r'^microservice/$', views.ServiceApi.as_view(), name='api_microservice'),

使用 python manage.py shell 打开控制台,新建几个服务,然后启动django,打开 http://127.0.0.1:8000/home/ ,就能看到页面如下:

服务列表

创建服务

通过该页面只能进行服务的查看,接下来添加服务新增功能,在 ServiceApi 里增加 post 方法:

    def post(self, request):
        d = {}
        d['name'] = request.POST.get('name', '')
        d['language'] = request.POST.get('language', 'cpp')
        d['description'] = request.POST.get('description', '')
        d['build_orig'] = request.POST.get('build_orig', 'git')
        d['build_url'] = request.POST.get('build_url', '')

        if not d['name'] or not d['build_url']:
            return JsonResponse({'msg': '必填项不能为空'}, status=417)

        try:
            Service.objects.get(name=d['name'])
        except Service.DoesNotExist:
            d['created_by'] = request.user
            d['updated_by'] = request.user
            service = Service.objects.create(**d)
            return JsonResponse({}, status=201)
        else:
            return JsonResponse({'msg': '该服务已存在'}, status=409)

相应的页面也要做一些修改,以下内容添加到 templates/microservice/service.html 头部

<div class="layui-row">
    <div class="layui-col-md2 layui-col-md-offset4">
      <button id="create-form-button" class="layui-btn" style="float: right;">
        <i class="layui-icon layui-icon-add-1"></i> 添加
      </button>
    </div>
  </div>

  <!-- 需要弹出的添加界面 -->
  <div class="layui-row" id="service-create" style="display: none;">
    <div class="layui-col-md10">
      <form id="service-create-form" class="layui-form" lay-filter="create"> <!-- 提示:如果你不想用form,你可以换成div等任何一个普通元素 -->
        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 名称</label>
          <div class="layui-input-block">
            <input type="text"
                   name="name"
                   placeholder="请输入服务名称"
                   autocomplete="off"
                   class="layui-input"
                   lay-verify="name"
            />
          </div>
        </div>

        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 语言类型</label>
          <div class="layui-input-block" >
            <input type="radio" lay-filter="create-lauguage" name="language" value="cpp" title="cpp" checked>
            <input type="radio" lay-filter="create-lauguage" name="language" value="go" title="go" >
            <input type="radio" lay-filter="create-lauguage" name="language" value="other" title="其它" >
          </div>
        </div>

        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 构建来源</label>
          <div class="layui-input-block">
            <input type="radio" name="build_orig" value="git" title="git" checked>
          </div>
        </div>

        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 构建地址</label>
          <div class="layui-input-block">
            <input type="text"
                   name="build_url"
                   placeholder="请输入构建地址"
                   autocomplete="off"
                   class="layui-input"
                   lay-verify="build_url"
            />
          </div>
        </div>

        <div class="layui-form-item layui-form-text">
          <label class="layui-form-label">描述</label>
          <div class="layui-input-block">
            <textarea rows="5" name="description" placeholder="请输入内容" class="layui-textarea"></textarea>
          </div>
        </div>

        <div class="layui-form-item">
          <div class="layui-input-block">
            <button class="layui-btn layui-btn-primary my-cancel-button">取消</button>
            <button class="layui-btn" lay-submit="" lay-filter="create-form">立即提交</button>
          </div>
        </div>

      </form>
    </div>
  </div>

页面增加相关js代码:

      /*
       * 监听表格事件
       */
      // 排序事件
      table.on('sort(tableEvent)', function(obj){
        table.reload('serviceListTable', {
          initSort: obj //记录初始排序,如果不设的话,将无法标记表头的排序状态。
          ,where: { //请求参数(注意:这里面的参数可任意定义,并非下面固定的格式)
            sort_field: obj.field //排序字段
            ,order: obj.type //排序方式
          }
        });
      });

      table.on('tool(tableEvent)', function (obj) {
        //注:tool 是工具条事件名,tableEvent 是 table 原始容器的属性 lay-filter="对应的值"
        var data = obj.data //获得当前行数据
          , layEvent = obj.event; //获得 lay-event 对应的值
        if (layEvent === 'del') {
          handleDelete(obj);
        } else if (layEvent === 'edit') {
          handleModify(obj);
        }
      });

      function handleCreate(layerIdx, postdata) {
        var loadingLayerIdx = layer.load(2, {
          shade: [0.3]
        });
        $.ajax({
          url: '{% url "api_microservice" %}',
          type: "POST",
          dataType: "json",
          data: postdata,
        }).done(result => {
          layer.msg('提交成功', {
            icon: 1,
            time: 1000 //2秒关闭(如果不配置,默认是3秒)
          }, function () {
            handleReloadTable();
            layer.close(layerIdx);
          });
        }).fail((xhr, status, error) => {
          // 移除disabled属性  提交按钮可点击
          $('#service-create-form button[lay-submit]').removeClass('layui-btn-disabled');
          ajaxErrorHandle(xhr, status, error);
        }).always(() => {
          // close loading
          layer.close(loadingLayerIdx);
        })
        return false;
      }

      $('#create-form-button').click(function () {
        /* 再弹出添加界面 */
        var layerIdx = layer.open({
          type: 1,
          title: '新增',
          area: ["50%", "70%"],
          content: $("#service-create").html(),
          shadeClose: true, // 点击弹出层的shade可关闭弹出层
          success: () => {
            // 移除disabled属性
            $('#service-create-form button[lay-submit]').removeClass('layui-btn-disabled');
          }
        });

        // 取消时关闭弹出层
        $('#service-create-form .my-cancel-button').click(() => {
          layer.close(layerIdx);
          return false; // 阻止浏览器自动跳转
        })
        /* 渲染表单 */
        form.render(null, 'create');
        form.verify({
          name: (value) => {
            let val = value.trim();
            if (!val) {
              return '服务名不能为空';
            }
            if (!/[/^[\w.\-_]{3,40}$/.test(val)) {
              return '3~40个字符, 大小写字母数字和下划线小数点'
            }
          }
          , language: (value) => {
            let val = value.trim();
            if (!val) {
              return '不能为空';
            }
          }
          , build_url: (value) => {
            let val = value.trim();
            if (!val) {
              return '不能为空';
            } else if (!(val.startsWith('http://') || val.startsWith('https://'))) {
              return 'url地址格式不正确,必须以http/https开头';
            }
          }

        });
        //监听提交
        form.on('submit(create-form)', function (data) {
          $(this).addClass('layui-btn-disabled');
          handleCreate(layerIdx, data.field);
          return false; // 阻止浏览器自动跳转
        });
      });

创建服务

修改和删除

视图:

class ServiceManageApi(BaseApiView):
    """
    服务管理操作 修改、删除
    """

    def post(self, request, pk):
        params = request.POST
        d = {}
        d['description'] = params.get('description', '')
        d['language'] = params.get('language', '')
        d['build_url'] = params.get('build_url', '')
        d['updated_by'] = request.user
        d['updated'] = datetime.datetime.utcnow().replace(tzinfo=utc)

        try:
            Service.objects.filter(pk=pk).update(**d)
        except Service.DoesNotExist:
            return JsonResponse({'msg': '资源不存在'}, status=404)
        return JsonResponse({})

    def delete(self, request, pk):
        svcs = Service.objects.annotate(num_versions=Count('microserviceversion')).filter(pk=pk)
        if not svcs:
            return JsonResponse({'msg': '资源不存在'}, status=404)
        if len(svcs) > 1:
            return JsonResponse({'msg': '数据错误'}, status=417)
        if svcs[0].num_versions != 0:
            return JsonResponse({'msg': '该服务有关联的版本,不允许删除'}, status=417)

        svcs.delete()
        return JsonResponse({})

对应的urls:

url(r'^microservice/(?P<pk>[0-9]+)/$', views.ServiceManageApi.as_view(), name='api_microservice_manage'),

在页面table的后面增加操作,支持 修改删除

<script type="text/html" id="barDemo">
    <a class="layui-btn layui-btn-xs layui-btn-primary" lay-event="edit">编辑</a>
    <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">删除</a>
  </script>

<!-- 修改 -->
  <div class="layui-row" id="service-modify" style="display: none;">
    <div class="layui-col-md10">
      <form id="service-modify-form" class="layui-form" lay-filter="modify">
        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 名称</label>
          <div class="layui-input-block">
            <input type="text"
                   name="name"
                   placeholder="请输入服务名称"
                   autocomplete="off"
                   class="layui-input"
                   lay-verify="name"
                   disabled=""
                   style="background-color: #eee; cursor: not-allowed;"
            />
          </div>
        </div>

        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 语言类型</label>
          <div class="layui-input-block">
            <input type="radio" name="language" value="cpp" title="cpp" disabled>
            <input type="radio" name="language" value="go" title="go" disabled>
            <input type="radio" name="language" value="other" title="其它" disabled>
          </div>
        </div>

        <div class="layui-form-item">
          <label class="layui-form-label"><i class="layui-icon layui-icon-rate" style="color: red;"></i> 构建地址</label>
          <div class="layui-input-block">
            <input type="text"
                   name="build_url"
                   placeholder="请输入构建地址"
                   autocomplete="off"
                   class="layui-input"
                   lay-verify="build_url"
                   disabled=""
                   style="background-color: #eee; cursor: not-allowed;"
            />
          </div>
        </div>

        <div class="layui-form-item layui-form-text">
          <label class="layui-form-label">描述</label>
          <div class="layui-input-block">
            <textarea rows="5" name="description" placeholder="请输入内容" class="layui-textarea"></textarea>
          </div>
        </div>

        <div class="layui-form-item">
          <div class="layui-input-block">
            <button class="layui-btn layui-btn-primary my-cancel-button">取消</button>
            <button class="layui-btn" lay-submit="" lay-filter="modify-form">保存修改</button>
          </div>
        </div>

      </form>
    </div>
  </div>  

相应的js:

// 渲染表格的cols里增加
, {fixed: 'right', title: '操作', width: 300, align: 'center', toolbar: '#barDemo'}
      /*
       * 编辑 删除
       */
      function handleDelete(obj) {
        layer.confirm('确定删除?删除后不可恢复!', {icon: 0, title:'提示'}, function (index) {
          layer.close(index);
          var loadingLayerIdx = layer.load(2, {
            shade: [0.3]
          });
          var url = '{% url "api_microservice_manage" 0 %}';
          url = url.substr(0, url.lastIndexOf(0));
          url +=  obj.data.id + '/';
          $.ajax({
            url: url,
            type: "DELETE",
          }).done(result => {
            layer.msg('删除成功', {icon: 1});
            obj.del(); //删除对应行(tr)的DOM结构
          }).fail((xhr, status, error) => {
            layer.msg('删除失败: ' + (xhr.status >= 500 ? '服务器内部错误' : xhr.responseJSON.msg), {icon: 2});
          }).always(() => {
            layer.close(loadingLayerIdx);
          });
        })
      }

      function handleModify(obj) {
        /* 弹出修改界面 */
        var data = obj.data;
        var layerIdx = layer.open({
          type: 1, // 0(信息框,默认)1(页面层)2(iframe层)3(加载层)4(tips层)
          title: '修改服务',
          area: ["50%", "70%"],
          content: $("#service-modify").html(),
          shadeClose: true, // 点击弹出层的shade可关闭弹出层
          success: () => {
            // 移除disabled属性  提交按钮可点击
            $('#service-modify-form button[lay-submit]').removeClass('layui-btn-disabled');
          }
        });

        // 取消时关闭弹出层
        $('#service-modify-form .my-cancel-button').click(() => {
          layer.close(layerIdx);
          return false; // 阻止浏览器自动跳转
        })
         //表单初始赋值
        form.val('modify', {
          "name": data.name // "name": "value"
          ,"language": data.language
          ,"build_orig": data.build_orig
          ,"build_url": data.build_url
          ,"description": data.description
        })

        /* 渲染表单 */
        form.render(null, 'modify');
        form.render('select', 'modify');

        form.verify({
          build_url: (value) => {
            let val = value.trim();
            if (!val) {
              return '不能为空';
            } else if (!val.startsWith('http://') || !val.startsWith('https://')) {
              return 'url地址格式不正确,必须以http/https开头';
            }
          }
        });
        //监听提交
        form.on('submit(modify-form)', function (data) {
          $(this).addClass('layui-btn-disabled');
          var loadingLayerIdx = layer.load(2, {
            shade: [0.3]
          });
          var url = '{% url "api_microservice_manage" 0 %}';
          url = url.substr(0, url.lastIndexOf(0));
          url +=  obj.data.id + '/';

          $.ajax({
            url: url,
            type: "POST",
            dataType: "json",
            data: data.field,

          }).done(result => {
            layer.msg('提交成功', {
              icon: 1,
              time: 1000 //2秒关闭(如果不配置,默认是3秒)
            }, function () {
              layer.close(layerIdx);
              handleReloadTable(false);
            });
          }).fail((xhr, status, error) => {
            // 移除disabled属性  提交按钮可点击
            $('#service-modify-form button[lay-submit]').removeClass('layui-btn-disabled');
            layer.msg('提交失败: ' + (xhr.status >= 500 ? '服务器内部错误' : xhr.responseJSON.msg), {icon: 2});
          }).always(() => {
            layer.close(loadingLayerIdx);
          })
          return false;
        });
      }

页面效果图:

相关的代码在 这里

posted @ 2019-12-01 18:38  葡萄不吐皮  阅读(437)  评论(0编辑  收藏  举报