• CMDB资产管理系统


    一、远程服务器资产信息采集方案

    实现方案一:agent——server服务端模式

    应用场景:多应用于服务器数量多情况下,效率比ssh方式高

    客户端:

    ################### 方式一:Agent,每一台服务器一份 ####################
    import subprocess
    v1 = subprocess.getoutput('ipconfig') # 开启一个子进程
    value1= v1[20:30]

    v2 = subprocess.getoutput('dir') # 开启一个子进程
    value2= v2[0:5]

    import requests
    url = 'http://127.0.0.1:8001/asset.html'
    response = requests.post(url, data={'k1':value1, 'k2':value2}) # 发送数据
    print(response)


    服务端:
    url(r'^asset.html$', views.asset),
    1
    from django.shortcuts import render,HttpResponse

    def asset(request):
    if request.method == "POST":
    print(request.POST)
    # 写入到数据
    return HttpResponse('1002')
    else:
    return HttpResponse('姿势不对')

    实现方案二:通过SSH远程连接服务器,使用Paramiko创建中控机

    pip3 install paramiko

    应用场景:服务器数量不多的时候,运行效率相对低,但维护简单,不需要每台服务安装客户端
    客户端:
    # #################### 方式二:Paramiko,中控机放一份 ####################
    """
    - 远程连接服务器,执行命令,获取结果
    - 将结果发送API
    192.168.11.98
    """
    import paramiko

    # 创建SSH对象
    ssh = paramiko.SSHClient()
    # 允许连接不在know_hosts文件中的主机
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # 连接远程服务器
    ssh.connect(hostname='192.168.121.128', port=22, username='lh', password='152303832')
    # 执行命令
    stdin, stdout, stderr = ssh.exec_command('ls')
    # 获取命令结果
    result = stdout.read()
    # 关闭连接
    ssh.close()
    value = result[0:10]
    print(value)

    url = "http://127.0.0.1:8001/asset.html"
    import requests

    response = requests.post(url, data={'k1': value, 'k2': value})
    print(response.text)

    服务端:与agent服务端相同
    实现方案三:使用saltStack开源软件(Python开发)

    内部原理:采用消息队列实现,而非ssh,

    修改root用户密码:sudo passwd root
    切换到root用户:su root
    安装软件要用root权限

    分为master和salve两部分:

    master部分:
    - yum install salt-master # 安装master
    - interface: 192.168.121.128 # 对配置文件进行配置 /etc/salt/master, ip地址为master所在的服务器地址
    - service salt-master start # 启动服务

    salve部分:

    yum install salt-minion
    配置:master的 ip地址 # 对配置文件进行配置 /etc/salt/minion
    service salt-minion start
    再对所有savle进行授权操作:
    salt-key -L # 查看连接的salve
    salt-key -A # 将所有的salve进行授权,可以实现master控制所有的被授权过的salve所在的服务器

    最后执行命令:在master服务器上执行: salt “*” cmd.run “ifconfig” # 表示所有salve服务器执行ifconfig命令

    python代码实现:
    v3 = subprocess.getoutput(‘salt “*” cmd.run “ifconfig” ‘) # 开启一个子进程在本地运行cmd命令
    二、采集器部分开发
    目录结构:

    三、 高级配置文件处理
    可执行文件:start.py

    import os
    os.environ['USER_SETTINGS'] = "config.settings" # 将用户级别的配置文件路径添加到环境变量中

    from lib.conf.config import settings # 备注:需要将该导入放置在添加环境变量语句后面,否则报错
    print(settings.USER)

    用户自定义配置文件:settings

    """
    用户自定义配置文件
    """
    USER = 'lh' # 服务器登陆信息
    PWD = '152303832'

    内置配置文件:global_settings.py

    """
    内置配置文件
    """

    EMAIL = '152303832@qq.com'

    配置文件整合:config.py

    """
    整合用户配置文件和内置配置文件
    """
    import os
    import importlib
    from . import global_settings

    class Settings(object):
    def __init__(self):
    ######## 找到默认内置配置文件 ########
    for name in dir(global_settings): # 获得模块中的所有属性名列表
    if name.isupper():
    value = getattr(global_settings, name) # 反射获得模块中的属性值
    setattr(self, name, value) # 为传入的参数对象添加该属性名及属性值

    # ######## 找到自定义配置 ########
    # 根据字符串导入模块
    settings_module = os.environ.get('USER_SETTINGS') # 获得环境变量(内存)中的自定义配置文件的路径值
    if not settings_module:
    return

    custom_settings = importlib.import_module(settings_module) # 根据字符串导入应对的模块
    for name in dir(custom_settings):
    if name.isupper():
    value = getattr(custom_settings, name)
    setattr(self, name, value)

    settings = Settings() # 将实例化的对象作为属性值

    三、 CMDB可插拔插件制作
    settings.py 配置文件

    """
    用户自定义的配置文件
    """
    USER = 'lh' # 服务器登陆信息
    PWD = '152303832'

    import os

    BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    # ##############插件所需的配置参数################
    MODE = 'AGENT' # 采用agent模式采集服务器信息
    # MODE = 'SALT' # 采用salt模式采集服务器信息
    # MODE = 'SSH' # 采用SSH模式采集服务器信息

    DEBUG = True

    SSH_USER = 'root' # 连接远程服务器的用户名
    SSH_PWD = 'root' # 连接远程服务器的密码
    SSH_KEY = '/XX/XX/XX' # 通过公钥私钥来连接远程服务器实现免密登陆
    SSH_PORT = 22

    PLUGINS_DICT = { # 插件字典,通过字符串导入模块
    'basic': "src.plugins.basic.Basic",
    'board': "src.plugins.board.Board",
    'cpu': "src.plugins.cpu.Cpu",
    'disk': "src.plugins.disk.Disk",
    'memory': "src.plugins.memory.Memory",
    'nic': "src.plugins.nic.Nic",
    }

    # api接口 url地址
    # API = "http://www.oldboyedu.com"
    API = "http://127.0.0.1:8000/api/asset.html"

    # 用于服务器唯一标识符,防止服务器数量出现叠加错误
    CERT_PATH = os.path.join(BASEDIR, 'config', 'cert')

    config.py 配合配置文件

    """
    整合用户配置文件和内置配置文件
    """
    import os
    import importlib
    from . import global_settings


    class Settings(object):
    def __init__(self):
    ######## 找到默认内置配置文件 ########
    for name in dir(global_settings):
    if name.isupper():
    value = getattr(global_settings, name) # 反射获得模块中的属性值
    setattr(self, name, value) # 为传入的参数对象添加该属性名及属性值

    # ######## 找到自定义配置 ########
    # 根据字符串导入模块
    settings_module = os.environ.get('USER_SETTINGS') # 获得环境变量(内存)中的自定义配置文件的路径值
    if not settings_module:
    return

    custom_settings = importlib.import_module(settings_module) # 根据字符串导入应对的模块
    for name in dir(custom_settings):
    if name.isupper():
    value = getattr(custom_settings, name)
    setattr(self, name, value)


    settings = Settings() # 将实例化的对象作为属性值

    start.py启动文件

    import os
    import sys

    # 程序启动入口文件
    os.environ['USER_SETTINGS'] = "config.settings" # 将用户级别的配置文件路径添加到环境变量中

    from lib.conf.config import settings # 备注:需要将该导入放置在添加环境变量语句后面,否则报错

    # print(settings.USER)

    BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    sys.path.append(BASEDIR)

    from src import script

    if __name__ == '__main__':
    script.run()

    script.py脚本文件:

    from lib.conf.config import settings
    from .client import Agent
    from .client import SSHSALT


    def run():
    """
    根据配置文件中的内容选择不同的采集方式
    :param object:
    :return:
    """
    if settings.MODE == 'AGENT':
    obj = Agent()
    else:
    obj = SSHSALT()
    obj.excute()

    client.py客户端:

    import requests
    from lib.conf.config import settings
    from src.plugins import PluginManager
    import json
    from concurrent.futures import ThreadPoolExecutor


    class Base(object):
    """
    负责往api发送数据
    """

    def post_asset(self, server_info):
    # 将数据转换成json字符串格式发送
    requests.post(settings.API, json=server_info) # 数据封装在body中: 会在源码中自动转换 json.dumps(server_info)
    # headers= {'content-type':'application/json'}
    # request.body # 需从body中取出数据
    # json.loads(request.body)


    class Agent(Base):
    """
    用agent方式采集数据并提交到api
    """

    def excute(self):
    servier_info = PluginManager().exec_plugin() # 采集数据

    # 唯一标识符处理
    hostname = servier_info['basic']['data']['hostname'] # 获得主机名,用来验证唯一标识符
    certname = open(settings.CERT_PATH, 'r', encoding='utf-8').read().strip() # 获得服务器上的文件中的主机名
    if not certname:
    with open(settings.CERT_PATH, 'w', encoding='utf-8') as f: # 如果文件中不存在该主机名,表示该主机名未初始化,写入文件即可
    f.write(hostname)
    else:
    # 用文件中的主机名覆盖被用户修改过的主机名,防止出现主机重复导致数量叠加错误
    servier_info['basic']['data']['hostname'] = certname

    self.post_asset(servier_info) # 子类对象调用父类方法来发送数据


    class SSHSALT(Base):
    """
    用SSH方式和SALT方式采集数据和发送
    """

    def get_host(self): # 该方式先获取未采集过数据的主机列表
    response = requests.get(settings.API)
    result = json.load(response.text) # "{status:'True',data: ['c1.com','c2.com']}"
    if result['status']:
    return None
    return result['data']

    # 执行服务器信息采集,并将该信息发送给API
    def run(self, host):
    server_info = PluginManager(host).exec_plugin() # 该两种采集方式都需传入主机host信息
    self.post_asset(server_info)

    # 基于线程池实现并发采集资产
    def excute(self):
    host_list = self.get_host()
    # 开启线程池并发任务,一次使用10个线程同时完成任务即可,多了会占用更多的系统资源
    pool = ThreadPoolExecutor(10)
    for host in host_list:
    pool.submit(self.run, host) # 提交要执行的任务及对应的参数
    _init_.py文件:

    from lib.conf.config import settings
    import importlib
    import traceback


    class PluginManager(object):
    def __init__(self, hostname=None): # 为agent/salt模式预留的主机名参数值
    self.hostname = hostname
    self.plugin_dict = settings.PLUGINS_DICT

    self.mode = settings.MODE # 采集模式
    self.debug = settings.DEBUG
    if self.mode == 'SSH':
    self.ssh_user = settings.SSH_USER
    self.ssh_port = settings.SSH_PORT
    self.ssh_pwd = settings.SSH_PWD
    self.ssh_key = settings.SSH_KEY

    def exec_plugin(self):
    """
    获取所有插件,并执行插件中的方法获得返回值
    :return:
    """
    response = {}
    for k, v in self.plugin_dict.items():
    # 'basic': "src.plugins.basic.Basic",
    ret = {'stauts': True, 'data': None}
    try:
    module_path, class_name = v.rsplit('.', 1) # 切分字符串获得模块路径和类名
    m = importlib.import_module(module_path) # 根据字符串获得模块
    cls = getattr(m, class_name) # 通过类名字符串,反射获得模块中的类
    if hasattr(cls, 'initial'):
    obj = cls.initial()
    else:
    obj = cls()
    result = obj.process(self.command, self.debug) # result = "根据v获取类,并执行其方法采集资产"
    ret['data'] = result
    except Exception as e:
    ret['stauts'] = False
    # traceback.format_exc()获得具体的错误信息 k表示插件名称
    ret['data'] = '[%s][%s]采集数据出现错误:%s' % (
    self.hostname if self.hostname else 'AGENT', k, traceback.format_exc())
    response[k] = ret
    return response

    ######判断采集方法############
    def command(self, cmd):
    if self.mode == 'AGENT':
    return self.__agent(cmd)
    elif self.mode == 'SSH':
    return self.__ssh(cmd)
    elif self.mode == 'SALT':
    return self.__salt(cmd)
    else:
    raise Exception('模式只能是 AGENT/SSH/SALT')

    ########## 执行对应的采集方法##########
    def __agent(self, cmd): # 私有方法,只有当前类的对象可调用
    import subprocess
    output = subprocess.getoutput(cmd)
    return output

    def __ssh(self, cmd):
    import paramiko
    # 通过公钥私钥方式登陆远程服务器
    # private_key = paramiko.RSAKey.from_private_key_file(self.ssh_key)
    # ssh = paramiko.SSHClient()
    # ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    # ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, pkey=private_key)
    # stdin, stdout, stderr = ssh.exec_command(cmd)
    # result = stdout.read()
    # ssh.close()

    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy)
    ssh.connect(hostname=self.hostname, port=self.ssh_port, username=self.ssh_user, password=self.ssh_pwd)
    stdin, stdout, stderr = ssh.exec_command(cmd)
    result = stdout.read()
    ssh.close()
    return result

    # 通过salt方式获得远程服务器信息
    def __salt(self, cmd):
    salt_cmd = "salt '%s' cmd.run '%s'" % (self.hostname, cmd,)
    import subprocess
    output = subprocess.getoutput(salt_cmd)
    return output

    basic.py采集基础信息:

    class Basic:
    """
    获取服务器基本信息(服务器名称...)
    """

    def __init__(self):
    pass

    @classmethod
    def inital(cls): # 定义类方法,用于扩展,在执行init方法时提前执行的扩展方法
    return cls()

    def process(self, command_func, debug):
    if debug: # 用于在windows环境下的测试
    output = {
    'os_platform': "linux",
    'os_version': "CentOS release 6.6 (Final) Kernel on an m",
    'hostname': 'c1.com'
    }
    else: # 在linux系统下执行的命令
    output = {
    'os_platform': command_func("uname").strip(), # 采集系统名称
    'os_version': command_func("cat /etc/issue").strip().split(' ')[0], # 采集系统版本
    'hostname': command_func("hostname").strip(), # 采集系统版本名称
    }
    return output

    board.py采集主板信息:

    from lib.conf.config import settings
    import os


    class Board:
    """
    获取服务器主板信息
    """

    def __init__(self):
    pass

    @classmethod
    def initial(cls):
    return cls()

    def process(self, command_func, debug):
    if debug:
    output = open(os.path.join(settings.BASEDIR, 'files/board.out'), 'r', encoding='utf-8').read()
    else:
    output = command_func('sudo dmidecode -t1') # 调用command_func方法来执行对应的采集方式
    return self.parse(output)

    # 解析服务器信息结果
    def parse(self, content):
    result = {}
    key_map = {
    'Manufacturer': 'manufacturer',
    'Product Name': 'model',
    'Serial Number': 'sn',
    }
    for item in content.split(' '):
    row_data = item.strip().split(':')
    if len(row_data) == 2:
    if row_data[0] in key_map:
    result[key_map[row_data[0]]] = row_data[1].strip() if row_data[1] else row_data[1]

    return result

    cpu.py 采集CPU信息:

    import os
    from lib.conf.config import settings


    class Cpu:
    """
    获取服务器CPUT信息
    """

    def __init__(self):
    pass

    @classmethod
    def initial(cls):
    return cls()

    def process(self, command_func, debug):
    if debug:
    output = open(os.path.join(settings.BASEDIR, 'files/cpuinfo.out'), 'r', encoding='utf-8').read()
    else:
    output = command_func("cat /proc/cpuinfo")
    return self.parse(output)

    def parse(self, content):
    """
    解析shell命令返回结果
    :param content: shell 命令结果
    :return:解析后的结果
    """
    response = {'cpu_count': 0, 'cpu_physical_count': 0, 'cpu_model': ''}

    cpu_physical_set = set()

    content = content.strip()
    for item in content.split(' '):
    for row_line in item.split(' '):
    key, value = row_line.split(':')
    key = key.strip()
    if key == 'processor':
    response['cpu_count'] += 1
    elif key == 'physical id':
    cpu_physical_set.add(value)
    elif key == 'model name':
    if not response['cpu_model']:
    response['cpu_model'] = value
    response['cpu_physical_count'] = len(cpu_physical_set)

    return response

    disk.py 采集硬盘信息:

    import re
    import os
    from lib.conf.config import settings


    class Disk:
    """
    获取服务器硬盘信息
    """

    def __init__(self):
    pass

    @classmethod
    def initial(cls):
    return cls()

    def process(self, command_func, debug):
    if debug:
    output = open(os.path.join(settings.BASEDIR, 'files/disk.out'), 'r', encoding='utf-8').read()
    else:
    output = command_func("sudo MegaCli -PDList -aALL")
    return self.parse(output)

    def parse(self, content):
    """
    解析shell命令返回结果
    :param content: shell 命令结果
    :return:解析后的结果
    """
    response = {}
    result = []
    for row_line in content.split(" "):
    result.append(row_line)
    for item in result:
    temp_dict = {}
    for row in item.split(' '):
    if not row.strip():
    continue
    if len(row.split(':')) != 2:
    continue
    key, value = row.split(':')
    name = self.mega_patter_match(key)
    if name:
    if key == 'Raw Size':
    raw_size = re.search('(d+.d+)', value.strip())
    if raw_size:

    temp_dict[name] = raw_size.group()
    else:
    raw_size = '0'
    else:
    temp_dict[name] = value.strip()
    if temp_dict:
    response[temp_dict['slot']] = temp_dict
    return response

    @staticmethod
    def mega_patter_match(needle):
    grep_pattern = {'Slot': 'slot', 'Raw Size': 'capacity', 'Inquiry': 'model', 'PD Type': 'pd_type'}
    for key, value in grep_pattern.items():
    if needle.startswith(key):
    return value
    return False

    memory.py 采集内存信息:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import os
    from lib import convert
    from lib.conf.config import settings


    class Memory(object):
    """
    获取服务器内存信息
    """

    def __init__(self):
    pass

    @classmethod
    def initial(cls):
    return cls()

    def process(self, command_func, debug):
    if debug:
    output = open(os.path.join(settings.BASEDIR, 'files/memory.out'), 'r', encoding='utf-8').read()

    else:
    output = command_func("sudo dmidecode -q -t 17 2>/dev/null")
    return self.parse(output)

    def parse(self, content):
    """
    解析shell命令返回结果
    :param content: shell 命令结果
    :return:解析后的结果
    """
    ram_dict = {}
    key_map = {
    'Size': 'capacity',
    'Locator': 'slot',
    'Type': 'model',
    'Speed': 'speed',
    'Manufacturer': 'manufacturer',
    'Serial Number': 'sn',

    }
    devices = content.split('Memory Device')
    for item in devices:
    item = item.strip()
    if not item:
    continue
    if item.startswith('#'):
    continue
    segment = {}
    lines = item.split(' ')
    for line in lines:
    if not line.strip():
    continue
    if len(line.split(':')):
    key, value = line.split(':')
    else:
    key = line.split(':')[0]
    value = ""
    if key in key_map:
    if key == 'Size':
    segment[key_map['Size']] = convert.convert_mb_to_gb(value, 0)
    else:
    segment[key_map[key.strip()]] = value.strip()

    ram_dict[segment['slot']] = segment

    return ram_dict

    采集网卡信息:

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import os
    import re
    from lib.conf.config import settings


    class Nic(object):
    """
    获取服务器网卡信息
    """

    def __init__(self):
    pass

    @classmethod
    def initial(cls):
    return cls()

    def process(self, command_func, debug):
    if debug:
    output = open(os.path.join(settings.BASEDIR, 'files/nic.out'), 'r', encoding='utf-8').read()
    interfaces_info = self._interfaces_ip(output)
    else:
    interfaces_info = self.linux_interfaces(command_func)

    self.standard(interfaces_info)

    return interfaces_info

    def linux_interfaces(self, command_func):
    '''
    Obtain interface information for *NIX/BSD variants
    '''
    ifaces = dict()
    ip_path = 'ip'
    if ip_path:
    cmd1 = command_func('sudo {0} link show'.format(ip_path))
    cmd2 = command_func('sudo {0} addr show'.format(ip_path))
    ifaces = self._interfaces_ip(cmd1 + ' ' + cmd2)
    return ifaces

    def which(self, exe):
    def _is_executable_file_or_link(exe):
    # check for os.X_OK doesn't suffice because directory may executable
    return (os.access(exe, os.X_OK) and
    (os.path.isfile(exe) or os.path.islink(exe)))

    if exe:
    if _is_executable_file_or_link(exe):
    # executable in cwd or fullpath
    return exe

    # default path based on busybox's default
    default_path = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin'
    search_path = os.environ.get('PATH', default_path)
    path_ext = os.environ.get('PATHEXT', '.EXE')
    ext_list = path_ext.split(';')

    search_path = search_path.split(os.pathsep)
    if True:
    # Add any dirs in the default_path which are not in search_path. If
    # there was no PATH variable found in os.environ, then this will be
    # a no-op. This ensures that all dirs in the default_path are
    # searched, which lets salt.utils.which() work well when invoked by
    # salt-call running from cron (which, depending on platform, may
    # have a severely limited PATH).
    search_path.extend(
    [
    x for x in default_path.split(os.pathsep)
    if x not in search_path
    ]
    )
    for path in search_path:
    full_path = os.path.join(path, exe)
    if _is_executable_file_or_link(full_path):
    return full_path

    return None

    def _number_of_set_bits_to_ipv4_netmask(self, set_bits): # pylint: disable=C0103
    '''
    Returns an IPv4 netmask from the integer representation of that mask.

    Ex. 0xffffff00 -> '255.255.255.0'
    '''
    return self.cidr_to_ipv4_netmask(self._number_of_set_bits(set_bits))

    def cidr_to_ipv4_netmask(self, cidr_bits):
    '''
    Returns an IPv4 netmask
    '''
    try:
    cidr_bits = int(cidr_bits)
    if not 1 <= cidr_bits <= 32:
    return ''
    except ValueError:
    return ''

    netmask = ''
    for idx in range(4):
    if idx:
    netmask += '.'
    if cidr_bits >= 8:
    netmask += '255'
    cidr_bits -= 8
    else:
    netmask += '{0:d}'.format(256 - (2 ** (8 - cidr_bits)))
    cidr_bits = 0
    return netmask

    def _number_of_set_bits(self, x):
    '''
    Returns the number of bits that are set in a 32bit int
    '''
    # Taken from http://stackoverflow.com/a/4912729. Many thanks!
    x -= (x >> 1) & 0x55555555
    x = ((x >> 2) & 0x33333333) + (x & 0x33333333)
    x = ((x >> 4) + x) & 0x0f0f0f0f
    x += x >> 8
    x += x >> 16
    return x & 0x0000003f

    def _interfaces_ip(self, out):
    '''
    Uses ip to return a dictionary of interfaces with various information about
    each (up/down state, ip address, netmask, and hwaddr)
    '''
    ret = dict()
    right_keys = ['name', 'hwaddr', 'up', 'netmask', 'ipaddrs']

    def parse_network(value, cols):
    '''
    Return a tuple of ip, netmask, broadcast
    based on the current set of cols
    '''
    brd = None
    if '/' in value: # we have a CIDR in this address
    ip, cidr = value.split('/') # pylint: disable=C0103
    else:
    ip = value # pylint: disable=C0103
    cidr = 32

    if type_ == 'inet':
    mask = self.cidr_to_ipv4_netmask(int(cidr))
    if 'brd' in cols:
    brd = cols[cols.index('brd') + 1]
    return (ip, mask, brd)

    groups = re.compile(' ? \d').split(out)
    for group in groups:
    iface = None
    data = dict()

    for line in group.splitlines():
    if ' ' not in line:
    continue
    match = re.match(r'^d*:s+([w.-]+)(?:@)?([w.-]+)?:s+<(.+)>', line)
    if match:
    iface, parent, attrs = match.groups()
    if 'UP' in attrs.split(','):
    data['up'] = True
    else:
    data['up'] = False
    if parent and parent in right_keys:
    data[parent] = parent
    continue

    cols = line.split()
    if len(cols) >= 2:
    type_, value = tuple(cols[0:2])

    iflabel = cols[-1:][0]
    if type_ in ('inet',):
    if 'secondary' not in cols:
    ipaddr, netmask, broadcast = parse_network(value, cols)
    if type_ == 'inet':
    if 'inet' not in data:
    data['inet'] = list()
    addr_obj = dict()
    addr_obj['address'] = ipaddr
    addr_obj['netmask'] = netmask
    addr_obj['broadcast'] = broadcast
    data['inet'].append(addr_obj)
    else:
    if 'secondary' not in data:
    data['secondary'] = list()
    ip_, mask, brd = parse_network(value, cols)
    data['secondary'].append({
    'type': type_,
    'address': ip_,
    'netmask': mask,
    'broadcast': brd,
    })
    del ip_, mask, brd
    elif type_.startswith('link'):
    data['hwaddr'] = value
    if iface:
    if iface.startswith('pan') or iface.startswith('lo') or iface.startswith('v'):
    del iface, data
    else:
    ret[iface] = data
    del iface, data
    return ret

    def standard(self, interfaces_info):

    for key, value in interfaces_info.items():
    ipaddrs = set()
    netmask = set()
    if not 'inet' in value:
    value['ipaddrs'] = ''
    value['netmask'] = ''
    else:
    for item in value['inet']:
    ipaddrs.add(item['address'])
    netmask.add(item['netmask'])
    value['ipaddrs'] = '/'.join(ipaddrs)
    value['netmask'] = '/'.join(netmask)
    del value['inet']

    四、资产入库数据库表设计
    from django.db import models


    class UserProfile(models.Model):
    """
    用户信息
    """
    name = models.CharField(u'姓名', max_length=32)
    email = models.EmailField(u'邮箱')
    phone = models.CharField(u'座机', max_length=32)
    mobile = models.CharField(u'手机', max_length=32)

    class Meta:
    verbose_name_plural = "用户表"

    def __str__(self):
    return self.name


    class AdminInfo(models.Model):
    """
    用户登陆相关信息
    """
    user_info = models.OneToOneField("UserProfile", on_delete='')
    username = models.CharField(u'用户名', max_length=64)
    password = models.CharField(u'密码', max_length=64)

    class Meta:
    verbose_name_plural = "管理员表"

    def __str__(self):
    return self.user_info.name


    class UserGroup(models.Model):
    """
    用户组
    """
    name = models.CharField(max_length=32, unique=True)
    users = models.ManyToManyField('UserProfile')

    class Meta:
    verbose_name_plural = "用户组表"

    def __str__(self):
    return self.name


    class BusinessUnit(models.Model):
    """
    业务线
    """
    name = models.CharField('业务线', max_length=64, unique=True)
    contact = models.ForeignKey('UserGroup', verbose_name='业务联系人', related_name='c', on_delete='')
    manager = models.ForeignKey('UserGroup', verbose_name='系统管理员', related_name='m', on_delete='')

    class Meta:
    verbose_name_plural = "业务线表"

    def __str__(self):
    return self.name


    class IDC(models.Model):
    """
    机房信息
    """
    name = models.CharField('机房', max_length=32)
    floor = models.IntegerField('楼层', default=1)

    class Meta:
    verbose_name_plural = "机房表"

    def __str__(self):
    return self.name


    class Tag(models.Model):
    """
    资产标签
    """
    name = models.CharField('标签', max_length=32, unique=True)

    class Meta:
    verbose_name_plural = "标签表"

    def __str__(self):
    return self.name


    class Asset(models.Model):
    """
    资产信息表,所有资产公共信息(交换机,服务器,防火墙等)
    """
    device_type_choices = (
    (1, '服务器'),
    (2, '交换机'),
    (3, '防火墙'),
    )
    device_status_choices = (
    (1, '上架'),
    (2, '在线'),
    (3, '离线'),
    (4, '下架'),
    )

    device_type_id = models.IntegerField(choices=device_type_choices, default=1)
    device_status_id = models.IntegerField(choices=device_status_choices, default=1)

    cabinet_num = models.CharField('机柜号', max_length=30, null=True, blank=True)
    cabinet_order = models.CharField('机柜中序号', max_length=30, null=True, blank=True)

    idc = models.ForeignKey('IDC', verbose_name='IDC机房', null=True, blank=True, on_delete='')
    business_unit = models.ForeignKey('BusinessUnit', verbose_name='属于的业务线', null=True, blank=True, on_delete='')

    tag = models.ManyToManyField('Tag',)

    latest_date = models.DateField(null=True)
    create_at = models.DateTimeField(auto_now_add=True)

    class Meta:
    verbose_name_plural = "资产表"

    def __str__(self):
    return "%s-%s-%s" % (self.idc.name, self.cabinet_num, self.cabinet_order)


    class Server(models.Model):
    """
    服务器信息
    """
    asset = models.OneToOneField('Asset', on_delete='')

    hostname = models.CharField(max_length=128, unique=True)
    sn = models.CharField('SN号', max_length=64, db_index=True)
    manufacturer = models.CharField(verbose_name='制造商', max_length=64, null=True, blank=True)
    model = models.CharField('型号', max_length=64, null=True, blank=True)

    manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True) # ip地址字段,有校验功能
    manage_ip = models.GenericIPAddressField('管理IP', null=True, blank=True) # ip地址字段,有校验功能

    os_platform = models.CharField('系统', max_length=16, null=True, blank=True)
    os_version = models.CharField('系统版本', max_length=16, null=True, blank=True)

    cpu_count = models.IntegerField('CPU个数', null=True, blank=True)
    cpu_physical_count = models.IntegerField('CPU物理个数', null=True, blank=True)
    cpu_model = models.CharField('CPU型号', max_length=128, null=True, blank=True)

    create_at = models.DateTimeField(auto_now_add=True, blank=True)

    class Meta:
    verbose_name_plural = "服务器表"

    def __str__(self):
    return self.hostname


    class NetworkDevice(models.Model):
    asset = models.OneToOneField('Asset', on_delete='')
    management_ip = models.CharField('管理IP', max_length=64, blank=True, null=True)
    vlan_ip = models.CharField('VlanIP', max_length=64, blank=True, null=True)
    intranet_ip = models.CharField('内网IP', max_length=128, blank=True, null=True)
    sn = models.CharField('SN号', max_length=64, unique=True)
    manufacture = models.CharField(verbose_name=u'制造商', max_length=128, null=True, blank=True)
    model = models.CharField('型号', max_length=128, null=True, blank=True)
    port_num = models.SmallIntegerField('端口个数', null=True, blank=True)
    device_detail = models.CharField('设置详细配置', max_length=255, null=True, blank=True)

    class Meta:
    verbose_name_plural = "网络设备"


    class Disk(models.Model):
    """
    硬盘信息
    """
    slot = models.CharField('插槽位', max_length=8)
    model = models.CharField('磁盘型号', max_length=32)
    capacity = models.FloatField('磁盘容量GB')
    pd_type = models.CharField('磁盘类型', max_length=32)
    server_obj = models.ForeignKey('Server',related_name='disk', on_delete='')

    class Meta:
    verbose_name_plural = "硬盘表"

    def __str__(self):
    return self.slot


    class NIC(models.Model):
    """
    网卡信息
    """
    name = models.CharField('网卡名称', max_length=128)
    hwaddr = models.CharField('网卡mac地址', max_length=64)
    netmask = models.CharField(max_length=64)
    ipaddrs = models.CharField('ip地址', max_length=256)
    up = models.BooleanField(default=False)
    server_obj = models.ForeignKey('Server',related_name='nic', on_delete='')


    class Meta:
    verbose_name_plural = "网卡表"

    def __str__(self):
    return self.name


    class Memory(models.Model):
    """
    内存信息
    """
    slot = models.CharField('插槽位', max_length=32)
    manufacturer = models.CharField('制造商', max_length=32, null=True, blank=True)
    model = models.CharField('型号', max_length=64)
    capacity = models.FloatField('容量', null=True, blank=True)
    sn = models.CharField('内存SN号', max_length=64, null=True, blank=True)
    speed = models.CharField('速度', max_length=16, null=True, blank=True)

    server_obj = models.ForeignKey('Server',related_name='memory', on_delete='')


    class Meta:
    verbose_name_plural = "内存表"

    def __str__(self):
    return self.slot


    class AssetRecord(models.Model):
    """
    资产变更记录,creator为空时,表示是资产汇报的数据。
    """
    asset_obj = models.ForeignKey('Asset', related_name='ar', on_delete='')
    content = models.TextField(null=True)# 新增硬盘
    creator = models.ForeignKey('UserProfile', null=True, blank=True, on_delete='') #
    create_at = models.DateTimeField(auto_now_add=True)


    class Meta:
    verbose_name_plural = "资产记录表"

    def __str__(self):
    return "%s-%s-%s" % (self.asset_obj.idc.name, self.asset_obj.cabinet_num, self.asset_obj.cabinet_order)


    class ErrorLog(models.Model):
    """
    错误日志,如:agent采集数据错误 或 运行错误
    """
    asset_obj = models.ForeignKey('Asset', null=True, blank=True, on_delete='')
    title = models.CharField(max_length=16)
    content = models.TextField()
    create_at = models.DateTimeField(auto_now_add=True)

    class Meta:
    verbose_name_plural = "错误日志表"

    def __str__(self):
    return self.title


    五、API获取资产并保存入库
    urls:

    url(r'api/', include('api.urls')),
    1
    api模块:

    url(r'^asset.html$', views.asset),
    1
    views:

    def asset(request):
    if request.method == 'POST':
    # 新资产信息
    server_info = json.loads(request.body.decode('utf-8')) # 将发送的数据解码成字符串,再通过json反序列化成字典格式
    hostname = server_info['basic']['data']['hostname'] # 获得采集器中发过来的信息中的主机名
    server_obj = models.Server.objects.filter(hostname = hostname)
    if not server_obj:
    return HttpResponse('当前主机名在资产中未录入')
    return HttpResponse('')

    七、资产入库处理 (以硬盘为例)
    views:

    from django.shortcuts import render
    import json
    from django.shortcuts import HttpResponse
    from repository import models


    # Create your views here.
    def asset(request):
    if request.method == 'POST':
    # 新资产信息
    server_info = json.loads(request.body.decode('utf-8')) # 将发送的数据解码成字符串,再通过json反序列化成字典格式
    hostname = server_info['basic']['data']['hostname'] # 获得采集器中发过来的信息中的主机名
    server_obj = models.Server.objects.filter(hostname=hostname).first() # 根据主机名获得服务器QuuerySet对象

    if not server_obj:
    return HttpResponse('当前主机名在资产中未录入')

    # 将硬盘信息入库
    # 'disk': {'stauts': True, 'data': {
    # '0': {'slot': '0', 'pd_type': 'SAS', 'capacity': '279.396',
    # 'model': 'SEAGATE ST300MM0006 LS08S0K2B5NV'},
    # '1': {'slot': '1', 'pd_type': 'SAS', 'capacity': '279.396',
    # 'model': 'SEAGATE ST300MM0006 LS08S0K2B5AH'},
    # '2': {'slot': '2', 'pd_type': 'SATA', 'capacity': '476.939',
    # 'model': 'S1SZNSAFA01085L Samsung SSD 850 PRO 512GB EXM01B6Q'},
    # '3': {'slot': '3', 'pd_type': 'SATA', 'capacity': '476.939',
    # 'model': 'S1AXNSAF912433K Samsung SSD 840 PRO Series DXM06B0Q'},
    # '4': {'slot': '4', 'pd_type': 'SATA', 'capacity': '476.939',
    # 'model': 'S1AXNSAF303909M Samsung SSD 840 PRO Series DXM05B0Q'},
    # '5': {'slot': '5', 'pd_type': 'SATA', 'capacity': '476.939',
    # 'model': 'S1AXNSAFB00549A Samsung SSD 840 PRO Series DXM06B0Q'}}},

    if not server_info['disk']['stauts']: # 采集信息中状态为False时,将错误信息添加到错误日志中
    models.ErrorLog.objects.create(content=server_info['disk']['data'], asset_obj=server_obj.asset,
    title='【%s】硬盘采集错误信息' % hostname)

    new_disk_dict = server_info['disk']['data'] # 获得服务器中最新的硬盘信息数据
    """
    {
    5: {'slot':5,capacity:476...}
    3: {'slot':3,capacity:476...}
    }
    """
    old_disk_list = models.Disk.objects.filter(server_obj=server_obj) # 获得服务器中之前的所有硬盘数据QuerySer对象列表
    """
    [
    Disk('slot':5,capacity:476...)
    Disk('slot':4,capacity:476...)
    ]
    """

    new_slot_list = list(new_disk_dict.keys()) # 获得最新硬盘数据中的插槽ID ,[0,1,2,3,4,5]

    old_slot_list = [] # 获得之前的硬盘数据中的插槽ID
    for item in old_disk_list:
    old_slot_list.append(item.slot)

    # 采用交集运算后的结果作为硬盘数据的 更新,获得共有的数据进行比较
    update_list = set(new_slot_list).intersection(old_slot_list) # 采用集合进行交集运算
    # 采用差集运算后的结果作为硬盘数据的 创建(新数据有,老数据没有)
    create_list = set(new_slot_list).difference(old_slot_list)
    # 采用差集运算后的结果作为硬盘数据的 删除(老数据有,新数据没有)
    del_list = set(old_slot_list).difference(new_slot_list)

    ###################从硬盘数据表中删除数据#################3
    if del_list:
    models.Disk.objects.filter(server_obj=server_obj, slot__in=del_list).delete()
    # 记录日志信息
    models.AssetRecord.objects.create(asset_obj=server_obj.asset, content='移除硬盘:%s' % ('、'.join(del_list)))

    ###################从硬盘数据表中增加数据#################
    record_list = []
    for slot in create_list:
    disk_dict = new_disk_dict[
    slot] # {'capacity': '476.939', 'slot': '4', 'model': 'S1AXNSAF303909M Samsung SSD 840 PRO Series
    disk_dict['server_obj'] = server_obj # 同时将服务器对象添加到该字典中一同添加到Disk数据表中
    models.Disk.objects.create(**disk_dict) # 以字典的形式添加数据到Disk数据表中

    # 组装硬盘变更记录信息
    temp = "新增硬盘:位置{slot},容量{capacity},型号:{model},类型:{pd_type}".format(**disk_dict)
    record_list.append(temp)
    if record_list:
    content = ';'.join(record_list) # 将所有变更信息拼接成一个字符串
    models.AssetRecord.objects.create(asset_obj=server_obj.asset, content=content) # 将变更记录添加到记录表中

    ###################从硬盘数据表中修改数据#################
    record_list = [] # 变更记录列表
    row_map = {'capacity': '容量', 'pd_type': '类型', 'model': '型号'}
    for slot in update_list:
    new_disk_row = new_disk_dict[slot] # 获得新采集过来的单条硬盘数据
    old_disk_row = models.Disk.objects.filter(slot=slot, server_obj=server_obj).first()
    for k, v in new_disk_row.items():
    # k: capacity; slot; pd_type; model
    # v: '476.939' 'xxies DXM05B0Q' 'SATA'
    value = getattr(old_disk_row, k) # 通过反射获得对象中属性的值
    if v != value: # 如果两者中的值不相等则表示需要更新
    setattr(old_disk_row, k, v) # 将对象中的属性值重新赋值
    record_list.append('槽位%s,%s由%s变更为%s' % (slot, row_map[k], value, v))
    old_disk_row.save() # 保存更新后的硬盘数据

    # 将变更信息保存到变更记录表中
    if record_list:
    content = ";".join(record_list)
    models.AssetRecord.objects.create(asset_obj=server_obj.asset, content=content)

    return HttpResponse('')

    八、牛x的API验证
    采集程序客户端的动态令牌生成:

    ############### 客户端生成并发送动态令牌完成API验证 ###############
    import requests
    import time
    import hashlib

    # 生成动态令牌
    ctime = time.time() # 动态时间戳
    key = "asdfasdfasdfasdf098712sdfs" # 假定API发送过来的静态令牌
    new_key = '%s|%s' % (key, ctime) # 在静态令牌基础加入动态时间,形成动态令牌
    print(ctime)

    # 动态令牌通过md5进行加密
    m = hashlib.md5() # 初始化md5
    m.update(bytes(new_key, encoding='utf-8')) # 将动态令牌转换成字节,将由md5进行计算
    md5_key = m.hexdigest() # 获得加密后的令牌
    print(md5_key)

    md5_time_key = '%s|%s' % (md5_key, ctime) # 将生成动态令牌所需的时间一同发给API,让API进行md5进行加密完成动态令牌的生成,以便完成动态令牌数据的比对

    # 将添加了时间的动态令牌添加到请求头中发往API
    response = requests.get('http://127.0.0.1:8000/api/asset.html', headers={'OpenKey': md5_time_key})
    print(response.text) # 获得响应结果

    API接收令牌完成身份验证:

    from django.shortcuts import render
    import json
    from django.shortcuts import HttpResponse
    from repository import models
    import time
    from server import settings
    import hashlib

    api_key_record = { # 访问记录表,由动态令牌作为key, 生成动态令牌中所需的时间+10s 作为value,表示该值保存10s
    # "1b96b89695f52ec9de8292a5a7945e38|1501472467.4977243":1501472477.4977243
    }

    def asset(request):
    client_md5_time_key = request.META.get('HTTP_OPENKEY') # 获取客户端通过请求头中发送过来的数据
    client_md5_key, client_ctime = client_md5_time_key.split('|') # 切分出动态令牌和时间
    client_ctime = float(client_ctime)
    server_time = time.time() # 获得服务器当前时间

    # 第一关验证:客户端第二次发送动态令牌的时间不能超过10s,完成第一层黑客攻击过滤
    if server_time - client_ctime > 10:
    return HttpResponse('第一关通过失败,时间超时')

    # 第二关验证:生成动态令牌的时间不匹配,防止黑客获得动态令牌,并通过第一关到达第二关
    temp = '%s|%s' % (settings.AUTH_KEY, client_ctime) # 从配置文件中读取出静态令牌
    # 完成服务端的动态令牌生成
    m = hashlib.md5()
    m.update(bytes(temp, encoding='utf-8'))
    server_md5_key = m.hexdigest()
    if server_md5_key != client_md5_key: # 如果两个动态令牌不相等
    return HttpResponse('第二关通过失败,生成服务端动态令牌中的时间与生成动态令牌中的时间不一致')

    # 对api_key_record记录表中进行数据更新,用于第三关验证
    for k in list(api_key_record.keys()):
    v = api_key_record[k]
    if server_time > v: # 如果服务器当前时间大于动态令牌有效时间,则删除该令牌,以便减少记录表容量占比
    del api_key_record[k]

    # 第三关:保持记录表中的唯一性,如果发送过请求,则其它的请求无效
    if client_md5_time_key in api_key_record:
    return HttpResponse('第三关通过失败,已经有人访问过了')
    else:
    api_key_record[client_md5_time_key] = client_ctime + 10 # 将是第一次的请求写入记录表中

    if request.method == 'GET':
    ys = 'api验证成功'
    return HttpResponse(ys)

    elif request.method == 'POST':
    略……

    九、对资产信息进行AES加密
    client客户端程序utils.py:

    from lib.conf.config import settings
    from Crypto.Cipher import AES


    def encrypt(message):
    """
    AES加密资产数据
    :param message:
    :return:
    """
    key = settings.DATA_KEY
    cipher = AES.new(key, AES.MODE_CBC, key) # 初始化AES对象
    ba_data = bytearray(message, encoding='utf-8') # 字符串编码成字节数组
    v1 = len(ba_data) # 计算需要加密的数据的长度
    v2 = v1 % 16 # AES只对16的倍数的字节长度进行加密
    if v2 == 0:
    v3 = 16
    else:
    v3 = 16 - v2
    for i in range(v3): # 需要为不是16倍数的字节数组添加字节,只至满足是16的倍数
    ba_data.append(v3) # 添加的字节内容采用需补齐的长度值
    final_data = ba_data.decode('utf-8') # 字节解码成字符串

    msg = cipher.encrypt(final_data) # 对字符串进行加密,成为字节
    return msg


    def decrypt(msg):
    """
    数据解密
    :param msg:
    :return:
    """
    from Crypto.Cipher import AES
    key = settings.DATA_KEY
    cipher = AES.new(key, AES.MODE_CBC, key)
    result = cipher.decrypt(msg)
    data = result[0:-result[-1]] # 获取补齐到16位长度前真正的内容,result[-1]表示补齐的长度值
    return str(data, encoding='utf-8') # 转换成字符串


    def auth():
    """
    API验证服务器身份
    :return:
    """
    import time
    import hashlib

    # 生成动态令牌
    ctime = time.time() # 动态时间戳
    key = "asdfasdfasdfasdf098712sdfs" # 假定API发送过来的令牌
    new_key = '%s|%s' % (key, ctime) # 在原有的随机字符串上加入动态时间,形成动态令牌
    print(ctime)
    # 动态令牌通过md5进行加密
    m = hashlib.md5() # 初始化md5
    m.update(bytes(new_key, encoding='utf-8')) # 将动态令牌转换成字节,将由md5进行计算
    md5_key = m.hexdigest() # 获得加密后的令牌
    print(md5_key)
    md5_time_key = '%s|%s' % (md5_key, ctime) # 将生成动态令牌所需的时间一同发给API,让API进行md5进行加密,以便完成加密数据的比对
    return md5_time_key

    client.py :

    class Base(object):
    """
    负责往api发送数据
    """

    def post_asset(self, server_info):
    # 将数据转换成json字符串格式发送
    data = encrypt(json.dumps(server_info)) # 将字典格式的数据转换成encrypt所需的字符串格式,然后加密
    response = requests.post(
    url=settings.API,
    data = data,
    headers={'OpenKey':auth(), 'Content-Type':'application/json'} #
    )
    print(response.text)

    server服务端程序中的api模块接收数据:

    def decrypt(msg):
    """
    对AES加密数据进行解密
    :param msg:
    :return:
    """
    from Crypto.Cipher import AES
    key = b'dfdsdfsasdfdsdfs' # 该Key值需与客户端加密所需的Key保持相同
    cipher = AES.new(key, AES.MODE_CBC, key)
    result = cipher.decrypt(
    msg) # result = b'xe8xa6x81xe5x8axa0xe5xafx86xe5x8axa0xe5xafx86xe5x8axa0sdfsd '
    data = result[0:-result[-1]]
    return str(data, encoding='utf-8')

    def asset(request):
    """
    接收客户端采集的资产信息
    :param request:
    :return:
    """
    if ……

    elif request.method == 'POST':
    server_info = decrypt(request.body) # 对客户端发送过来的AES加密数据进行解密
    server_info = json.loads(server_info) # 对字符串进行反序列化成字典
    print(server_info)

    十、CMDB后台管理之CURD插件数据格式化
    urls:

    url(r'^curd.html$', views.curd), # 进入到数据展示页面
    1
    views:

    def curd(request):
    """
    进入到curd.html页面
    :param request:
    :return:
    """
    return render(request, 'curd.html')

    html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
    </head>
    <body>
    <div style=" 700px; margin: 0 auto">
    <table class="table table-bordered table-striped">
    <thead id="tbHead">
    <tr>

    </tr>
    </thead>
    <tbody id="tbBody">

    </tbody>

    </table>

    </div>

    <script src="/static/jquery-1.12.4.js"></script>

    urls:

    url(r'^curd_json.html$', views.curd_json) # 通过页面加载时启动的js获得数据呈现在页面中
    1
    views:


    def curd_json(request):
    """
    进行数据结构处理
    :param request:
    :return:
    """
    table_config = [ # 配置文件,用于前端页面数据定制显示
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'text': {
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    }
    },
    {
    'q': 'hostname',
    'title': '主机名',
    'text': {
    'tpl': '{n1}-{n2}',
    'kwargs': {'n1': '@hostname', 'n2': '@id'}
    }
    },
    {
    'q': 'create_at',
    'title': '创建时间',
    'text': {
    'tpl': '{n1}',
    'kwargs': {'n1': '@create_at'}
    }
    },
    {
    'q': 'asset__cabinet_num', 'title': '机柜号',
    'text': {
    'tpl': "BJ-{n1}",
    'kwargs': {'n1': '@asset__cabinet_num'}
    }
    },

    {
    'q': 'asset__business_unit__name',
    'title': '业务线名称',
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@asset__business_unit__name'}
    }
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q':None,
    'title':'操作',
    'text':{
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs':{'nid':'@id'},
    }
    },
    ]

    # 组装数据库查询所需的字段
    value_list = []
    for row in table_config:
    if not row['q']:
    continue
    value_list.append(row['q'])

    from datetime import datetime
    from datetime import date
    class JsonCustomEncoder(json.JSONEncoder):
    """
    json扩展:针对日期格式数据进行自定义转换成字符串处理
    """
    def default(self, value):
    if isinstance(value, datetime):
    return value.strftime('%Y-%m-%d %H:%M:%S')
    elif isinstance(value, date):
    return value.strftime('%Y-%m-%d')
    else:
    return json.JSONEncoder.default(self, value) # 调用父类中的default方法


    server_list = models.Server.objects.values(*value_list) # 传入列表获得字典格式数据
    ret = {
    'server_list':list(server_list), # 将Querylist转换成列表
    'table_config':table_config,
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    html:

    <script src="/static/jquery-1.12.4.js"></script>
    <script>
    $(function () {
    {#通过ajax异步获得初始化数据#}
    initial();
    });

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
    return this.replace(/{(w+)}/g, function (s, i) {
    return args[i];
    });
    };

    {#页面加载时发送ajax请求#}
    function initial() {
    $.ajax({
    url: '/backend/curd_json.html',
    type: 'GET',
    {#将响应的字符串数据转换成字典格式#}
    dataType: 'JSON',
    success: function (arg) {
    {#生成表头字段#}
    initTableHeader(arg.table_config);
    {#生成表格数据#}
    initTableBody(arg.server_list, arg.table_config);
    }
    })
    }

    // {#生成表头字段#}
    function initTableHeader(tableConfig) {
    $('#tbHead').empty() // 清除该标签内的所有内容
    var tr = document.createElement('tr') // 生成tr标签
    // {#循环生成字段表头#}
    $.each(tableConfig, function (k, v) {
    if (v.display) { // 为Ture时需要展示
    var tag = document.createElement('th');
    tag.innerHTML = v.title
    $(tr).append(tag);
    }
    })
    $('#tbHead').append(tr);
    }

    {#生成表格数据信息#}
    function initTableBody(serverList, tableConfig) {
    $.each(serverList, function (k, row) {
    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */
    var tr = document.createElement('tr')
    $.each(tableConfig, function (kk, rrow) {
    // kk: 1 rrow:{'q':'id','title':'ID'}, // rrow.q = "id"
    // kk: . rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    // kk: . rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    var td = document.createElement('td');
    // rrow['q']
    // rrow['text']
    // rrow.text.tpl = "asdf{n1}sdf"
    // rrow.text.kwargs = {'n1':'@id','n2':'123'}
    var newKwargs = {}; // {'n1':'1','n2':'123'}
    $.each(rrow.text.kwargs,function(kkk,vvv){
    var av = vvv;
    {#@表示需要进行字符串格式化#}
    if (vvv[0] == '@') {
    {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}
    av = row[vvv.substring(1, vvv.length)];
    }
    newKwargs[kkk] = av;
    });

    {#通过自定义的扩展方法进行字符串格式化#}
    var newText = rrow.text.tpl.format(newKwargs);

    td.innerHTML = newText;
    $(tr).append(td)
    });
    $('#tbBody').append(tr);
    })
    }

    </script>

    十一、CMDB后台管理之封装基本插件
    views:

    # 略……
    def curd_json(request):
    """
    进行数据结构处理
    :param request:
    :return:
    """
    # q表示数据库查询字段,
    # title表示前端表格中的表头字段,
    # text用来将数据库中取出的值进行字符串格式化
    # display表示该字段在前端页面表格表头是否显示
    table_config = [ # 配置文件,用于前端页面数据定制显示
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'display':False,
    'text': {
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    }
    },
    {
    'q': 'hostname',
    'title': '主机名',
    'display': True,
    'text': {
    'tpl': '{n1}-{n2}',
    'kwargs': {'n1': '@hostname', 'n2': '@id'}
    }
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q':None,
    'title':'操作',
    'display': True,
    'text':{
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs':{'nid':'@id'},
    }
    },
    ]
    # 略……

    nb-list.js自定义Js函数插件:

    // 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
    (function (jq) {
    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
    return this.replace(/{(w+)}/g, function (s, i) {
    return args[i];
    });
    };

    // {#页面加载时发送ajax请求#}
    function initial(url) {
    $.ajax({
    url: url,
    type: 'GET',
    // {#将响应的字符串数据转换成字典格式#}
    dataType: 'JSON',
    success: function (arg) {
    // {#生成表头字段#}
    initTableHeader(arg.table_config);
    // {#生成表格数据#}
    initTableBody(arg.server_list, arg.table_config);
    }
    })
    }

    // {#生成表头字段#}
    function initTableHeader(tableConfig) {
    $('#tbHead').empty() // 清除该标签内的所有内容
    var tr = document.createElement('tr') // 生成tr标签
    // {#循环生成字段表头#}
    $.each(tableConfig, function (k, v) {
    if (v.display) { // 为Ture时需要展示
    var tag = document.createElement('th');
    tag.innerHTML = v.title
    $(tr).append(tag);
    }
    })
    $('#tbHead').append(tr);
    }

    // {#生成表格数据信息#}
    function initTableBody(serverList, tableConfig) {
    $.each(serverList, function (k, row) {
    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */

    var tr = document.createElement('tr')
    $.each(tableConfig, function (kk, rrow) {
    if (rrow.display) { // 是否需要展示该字段对应的内容
    // kk: 1 rrow:{'q':'id','title':'ID'}, // rrow.q = "id"
    // kk: . rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    // kk: . rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    var td = document.createElement('td');
    // rrow['q']
    // rrow['text']
    // rrow.text.tpl = "asdf{n1}sdf"
    // rrow.text.kwargs = {'n1':'@id','n2':'123'}
    var newKwargs = {}; // {'n1':'1','n2':'123'}
    $.each(rrow.text.kwargs, function (kkk, vvv) {
    var av = vvv;
    // {#@表示需要进行字符串格式化#}
    if (vvv[0] == '@') {
    // {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}
    av = row[vvv.substring(1, vvv.length)];
    }
    newKwargs[kkk] = av;
    });

    // {#通过自定义的扩展方法进行字符串格式化#}
    var newText = rrow.text.tpl.format(newKwargs);

    td.innerHTML = newText;
    $(tr).append(td)
    }
    });
    $('#tbBody').append(tr);
    })
    }

    jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用
    xx: function (url) {
    // {#通过ajax异步请求获得初始化数据#}
    initial(url)
    }
    })
    })(jQuery) // 传入jQeury对象

    html:

    # 略……
    <script src = "/static/nb-list.js"></script>
    <script>
    {#调用自定义的jQuery函数#}
    $.xx('/backend/curd_json.html');
    </script>
    # 略……

    十二、CMDB后台管理之CURD插件显示cho(数字对应的字符串)
    urls:

    # 资产信息展示
    url(r'^asset.html$', views.asset),
    url(r'^asset_json.html$', views.asset_json),

    views:

    # 资产信息展示
    def asset(request):
    """
    跳转到资产信息展示页面
    :param request:
    :return:
    """
    return render(request, 'asset.html',)

    def asset_json(reqeust):
    """
    ajax获取资产信息数据
    :param reqeust:
    :return:
    """
    # @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示
    table_config = [ # 配置文件
    {
    'q':'id',
    'title':'ID',
    'display':False,
    'text':{
    'tpl': "{n1}",
    'kwargs': {'n1': '@id'}
    },
    },
    {
    'q': 'device_type_id',
    'title': '资产类型',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_type_choices'}
    }
    },
    {
    'q': 'device_status_id',
    'title': '状态',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_status_choices'}
    }
    },
    {
    'q': 'cabinet_num',
    'title': '机柜号',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@cabinet_num'}
    }
    },
    {
    'q': 'idc__name',
    'title': '机房',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@idc__name'}
    }
    },
    # 页面显示:标题:操作;删除,编辑:a标签
    {
    'q': None,
    'title': '操作',
    'display': True,
    'text': {
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs': {'nid': '@id'}
    }
    },
    ]
    # 用于数据库查询字段
    value_list = []
    for row in table_config:
    if not row['q']:
    continue
    value_list.append(row['q'])
    server_list = models.Asset.objects.values(*value_list)

    # global_dict用于生成选择元组中的数字对应的字符串
    ret={
    'server_list': list(server_list),
    'table_config':table_config,
    'global_dict':{
    'device_type_choices':models.Asset.device_type_choices,
    'device_status_choices':models.Asset.device_status_choices,
    }
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title></title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
    </head>
    <body>
    <div style=" 700px;margin: 0 auto">
    <h1>资产列表</h1>
    <table class="table table-bordered table-striped">
    <thead id="tbHead">
    <tr>

    </tr>
    </thead>
    <tbody id="tbBody">

    </tbody>
    </table>


    </div>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/nb-list.js"></script>
    <script>
    $.xx('/backend/asset_json.html');
    </script>
    </body>
    </html>

    nb-list.js自定义js文件:

    /**
    * Created by Administrator on 2017/8/2.
    */
    (function (jq) {
    // 全局常量
    var GLOBAL_DICT = {};
    /*
    {
    'device_type_choices': (
    (1, '服务器'),
    (2, '交换机'),
    (3, '防火墙'),
    )
    'device_status_choices': (
    (1, '上架'),
    (2, '在线'),
    (3, '离线'),
    (4, '下架'),
    )
    }
    */

    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
    return this.replace(/{(w+)}/g, function (s, i) {
    return args[i];
    });
    };

    function initial(url) {
    $.ajax({
    url: url,
    type: 'GET', // 获取数据
    dataType: 'JSON',
    success: function (arg) {
    $.each(arg.global_dict,function(k,v){
    GLOBAL_DICT[k] = v
    });

    /*
    {
    'server_list':list(server_list), # 所有数据
    'table_config':table_config # 所有配置
    'global_dict':{
    'device_type_choices': (
    (1, '服务器'),
    (2, '交换机'),
    (3, '防火墙'),
    )
    'device_status_choices': (
    (1, '上架'),
    (2, '在线'),
    (3, '离线'),
    (4, '下架'),
    )
    }
    }
    */
    initTableHeader(arg.table_config);
    initTableBody(arg.server_list, arg.table_config);
    }
    })
    }

    // {#生成表头字段#}
    function initTableHeader(tableConfig) {
    $('#tbHead').empty() // 清除该标签内的所有内容
    var tr = document.createElement('tr') // 生成tr标签
    // {#循环生成字段表头#}
    $.each(tableConfig, function (k, v) {
    if (v.display) { // 为Ture时需要展示
    var tag = document.createElement('th');
    tag.innerHTML = v.title
    $(tr).append(tag);
    }
    })
    $('#tbHead').append(tr);
    }

    function initTableBody(serverList, tableConfig) {
    /*
    serverList = [
    {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-},
    ]
    */
    $.each(serverList, function (k, row) {
    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */
    var tr = document.createElement('tr');
    $.each(tableConfig, function (kk, rrow) {
    // kk: 1 rrow:{'q':'id','title':'ID'}, // rrow.q = "id"
    // kk: . rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    // kk: . rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    if (rrow.display) {
    var td = document.createElement('td');
    /*
    if(rrow['q']){
    td.innerHTML = row[rrow.q];
    }else{
    td.innerHTML = rrow.text;
    }*/
    // rrow['q']
    // rrow['text']
    // rrow.text.tpl = "asdf{n1}sdf"
    // rrow.text.kwargs = {'n1':'@id','n2':'@@123'}
    var newKwargs = {}; // {'n1':'1','n2':'123'}
    $.each(rrow.text.kwargs, function (kkk, vvv) {
    var av = vvv;
    if(vvv.substring(0,2) == '@@'){
    var global_dict_key = vvv.substring(2,vvv.length);
    var nid = row[rrow.q];
    console.log(nid,global_dict_key); // 1 "device_type_choices"
    $.each(GLOBAL_DICT[global_dict_key],function(gk,gv){
    if(gv[0] == nid){
    av = gv[1];
    }
    })
    }
    else if (vvv[0] == '@') {
    av = row[vvv.substring(1, vvv.length)];
    }
    newKwargs[kkk] = av;
    });
    var newText = rrow.text.tpl.format(newKwargs);
    td.innerHTML = newText;
    $(tr).append(td);
    }
    });
    $('#tbBody').append(tr);

    })
    }

    jq.extend({
    xx: function (url) {
    initial(url);
    }
    })
    })(jQuery);

    十三、CMDB后台管理之CURD插件定制属性
    views:

    def asset_json(reqeust):
    """
    ajax获取资产信息数据
    :param reqeust:
    :return:
    """
    # @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示
    table_config = [ # 配置文件
    {
    'q':'id',
    'title':'ID',
    'display':False,
    'text':{
    'tpl': "{n1}",
    'kwargs': {'n1': '@id'}
    },
    'attrs':{'k1':'v1', 'k2':'@id'}
    },
    {
    'q': 'device_type_id',
    'title': '资产类型',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_type_choices'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    {
    'q': 'device_status_id',
    'title': '状态',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_status_choices'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    {
    'q': 'cabinet_num',
    'title': '机柜号',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@cabinet_num'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },

    {
    'q': 'idc__name',
    'title': '机房',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@idc__name'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    # 页面显示:标题:操作;删除,编辑:a标签
    {
    'q': None,
    'title': '操作',
    'display': True,
    'text': {
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs': {'nid': '@id'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    ]
    # 略……

    nb-list.js自定义js扩展文件:

    # 略……
    // 在标签中添加属性
    $.each(rrow.attrs, function (atkey, atval) {
    // 如果@
    if(atval[0] == '@'){
    td.setAttribute(atkey, row[atval.substring(1, atval.length)]);
    }else {
    td.setAttribute(atkey, atval);
    }
    });

    $(tr).append(td)

    # 略……

    十四、增删改查插件之当前行进入编辑模式
    views:

    from django.shortcuts import render
    import json
    from repository import models
    from django.shortcuts import HttpResponse
    from datetime import datetime
    from datetime import date
    # Create your views here.
    class JsonCustomEncoder(json.JSONEncoder):
    """
    json扩展:针对日期格式数据进行自定义转换成字符串处理
    """

    def default(self, value):
    if isinstance(value, datetime):
    return value.strftime('%Y-%m-%d %H:%M:%S')
    elif isinstance(value, date):
    return value.strftime('%Y-%m-%d')
    else:
    return json.JSONEncoder.default(self, value) # 调用父类中的default方法


    def curd(request):
    """
    进入到curd.html页面
    :param request:
    :return:
    """
    return render(request, 'curd.html')


    def curd_json(request):
    """
    ajax请求方法
    :param request:
    :return:
    """

    table_config = [ # 配置文件,用于前端页面数据定制显示
    # 生成checkbox多选框字段
    {
    'q': None, # 不作为数据库查询字段
    'title': '选择',
    'display': True,
    'text': {
    'tpl': "<input type='checkbox' value='{n1}' />",
    'kwargs': {'n1': '@id',}
    },
    'attrs': {'nid': '@id'}
    },

    # 生成id字段
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'display':False,# display表示该字段在前端页面表格表头是否显示
    'text': { # text用来将数据库中取出的值进行字符串格式化
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符模板
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    },
    'attrs':{'k1':'v1','k2':'@hostname'} # 为前端标签添加属性及属性值
    },
    {
    'q': 'hostname',
    'title': '主机名',
    'display': True,
    'text': {
    'tpl': '{n1}-{n2}',
    'kwargs': {'n1': '@hostname', 'n2': '@id'}
    },
    'attrs':{'edit-enable':'true', 'k2':'@hostname'} # edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q':None,
    'title':'操作',
    'display': True,
    'text':{
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs':{'nid':'@id'},
    },
    'attrs': {'k1': 'v1', 'k2': '@hostname'}
    },
    ]

    # 组装数据库查询所需的字段
    value_list = []
    for row in table_config:
    if not row['q']:
    continue
    value_list.append(row['q'])

    server_list = models.Server.objects.values(*value_list) # 传入列表获得字典格式数据
    ret = {
    'server_list':list(server_list), # 将Querylist转换成列表
    'table_config':table_config,
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    # 资产信息展示
    def asset(request):
    """
    跳转到资产信息展示页面
    :param request:
    :return:
    """
    return render(request, 'asset.html',)

    def asset_json(reqeust):
    """
    ajax获取资产信息数据
    :param reqeust:
    :return:
    """
    # @代表需要字符串格式化,@@用于将数字结果转换成对应字符串展示
    table_config = [ # 配置文件
    # 生成checkbox多选框字段
    {
    'q': None, # 不作为数据库查询字段
    'title': '选择',
    'display': True,
    'text': {
    'tpl': "<input type='checkbox' value='{n1}' />",
    'kwargs': {'n1': '@id', }
    },
    'attrs': {'nid': '@id'}
    },

    {
    'q':'id',
    'title':'ID',
    'display':False,
    'text':{
    'tpl': "{n1}",
    'kwargs': {'n1': '@id'}
    },
    'attrs':{'k1':'v1', 'k2':'@id'}
    },
    {
    'q': 'device_type_id',
    'title': '资产类型',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_type_choices'}
    },
    # origin表示数据库字段id对应的值, global_key表示数据库中下拉框的数据
    'attrs':{'k1':'v1','origin':'@device_type_id','edit-enable':'true','edit-type':'select','global_key':'device_type_choices'}
    },
    {
    'q': 'device_status_id',
    'title': '状态',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@@device_status_choices'}
    },
    'attrs':{'edit-enable':'true','origin': '@device_status_id','edit-type':'select','global_key':'device_status_choices' }
    },
    {
    'q': 'cabinet_num',
    'title': '机柜号',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@cabinet_num'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },

    {
    'q': 'idc__name',
    'title': '机房',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@idc__name'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    # 页面显示:标题:操作;删除,编辑:a标签
    {
    'q': None,
    'title': '操作',
    'display': True,
    'text': {
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs': {'nid': '@id'}
    },
    'attrs': {'k1': 'v1', 'k2': '@id'}
    },
    ]
    # 用于数据库查询字段
    value_list = []
    for row in table_config:
    if not row['q']:
    continue
    value_list.append(row['q'])
    server_list = models.Asset.objects.values(*value_list)

    # global_dict用于生成选择元组中的数字对应的字符串
    ret={
    'server_list': list(server_list),
    'table_config':table_config,
    'global_dict':{
    'device_type_choices':models.Asset.device_type_choices,
    'device_status_choices':models.Asset.device_status_choices,
    }
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    nb-list.js 自定义Js文件:

    // 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
    (function (jq) {
    var GLOBAL_DICT={};
    /*
    {
    'device_type_choices': (
    (1, '服务器'),
    (2, '交换机'),
    (3, '防火墙'),
    )
    'device_status_choices': (
    (1, '上架'),
    (2, '在线'),
    (3, '离线'),
    (4, '下架'),
    )
    }
    */


    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
    return this.replace(/{(w+)}/g, function (s, i) {
    return args[i];
    });
    };

    // {#页面加载时自动发送ajax请求#}
    function initial(url) {
    $.ajax({
    url: url,
    type: 'GET',
    // {#将响应的字符串数据转换成字典格式#}
    dataType: 'JSON',
    success: function (arg) {
    // 将 (1, '服务器')……等数据作成全局常量
    $.each(arg.global_dict, function (k ,v) {
    GLOBAL_DICT[k] = v
    });
    // {#生成表头字段#}
    initTableHeader(arg.table_config);
    // {#生成表格数据#}
    initTableBody(arg.server_list, arg.table_config);
    }
    })
    }

    // {#生成表头字段#}
    function initTableHeader(tableConfig) {
    $('#tbHead').empty() // 清除该标签内的所有内容
    var tr = document.createElement('tr') // 生成tr标签
    // {#循环生成字段表头#}
    $.each(tableConfig, function (k, v) {
    if (v.display) { // 为Ture时需要展示
    var tag = document.createElement('th');
    tag.innerHTML = v.title
    $(tr).append(tag);
    }
    })
    $('#tbHead').append(tr);
    }

    // {#生成表格数据信息#}
    function initTableBody(serverList, tableConfig) {

    $.each(serverList, function (k, row) { // 循环查询出来数据表中所有的数据

    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */
    var tr = document.createElement('tr')
    $.each(tableConfig, function (kk, rrow) {
    if (rrow.display) { // 是否需要展示该字段对应的内容
    // kk: 1 rrow:{'q':'id','title':'ID'}, // rrow.q = "id"
    // kk: . rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    // kk: . rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    var td = document.createElement('td');
    // rrow['q']
    // rrow['text']
    // rrow.text.tpl = "asdf{n1}sdf"
    // rrow.text.kwargs = {'n1':'@id','n2':'@@123'}
    var newKwargs = {}; // {'n1':'1','n2':'123'}
    $.each(rrow.text.kwargs, function (kkk, vvv) { // 循环字典
    var av = vvv;
    if(vvv.substring(0,2) == '@@'){ // 生成数字对应的字符串值
    var global_dict_key = vvv.substring(2, vvv.length); // 获得数据表中的字段名 例device_type_choices
    var nid = row[rrow.q] // 通过自定义的配置字典,获得数据表中该条数据的id值
    $.each(GLOBAL_DICT[global_dict_key], function (gk, gv) {
    if(gv[0] == nid){
    av = gv[1]; // av = '服务器'
    }
    })
    }
    // {#@表示需要进行字符串格式化#}
    else if (vvv[0] == '@') {
    // {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}
    av = row[vvv.substring(1, vvv.length)];
    }
    newKwargs[kkk] = av;
    });

    // {#通过自定义的扩展方法进行字符串格式化#}
    var newText = rrow.text.tpl.format(newKwargs);
    td.innerHTML = newText;

    // 在标签中添加属性
    $.each(rrow.attrs, function (atkey, atval) {
    // 如果@
    if(atval[0] == '@'){
    td.setAttribute(atkey, row[atval.substring(1, atval.length)]);
    }else {
    td.setAttribute(atkey, atval);
    }
    });

    $(tr).append(td)
    }
    });
    $('#tbBody').append(tr);
    })
    }

    // 进入编辑模式
    function trIntoEdit($tr) {
    $tr.find('td[edit-enable="true"]').each(function () { // 找到tr标签下所有td标签中属性为edit-enable=true的元素,并循环它
    // $(this) 每一个td标签
    var editType = $(this).attr('edit-type'); // 从配置列表中获得编辑类型
    if(editType == 'select'){
    // 生成下拉框,找到数据源
    var deviceTypeChoices = GLOBAL_DICT[$(this).attr('global_key')];
    // 生成select下拉框标签
    var selectTag = document.createElement('select');
    var origin = $(this).attr('origin'); // 获得当前标签中的origin属性的值
    $.each(deviceTypeChoices, function (k, v) { // v的值为 (1, '服务器'),
    var option = document.createElement('option');
    $(option).text(v[1]); // 为option标签添加文本值
    $(option).val(v[0]); // 为option标签添加属性值
    if(v[0] == origin){
    // 默认选中原来的值
    $(option).prop('selected', true);
    }
    $(selectTag).append(option);
    });
    $(this).html(selectTag)
    }else {
    // 获取原来td中的文本内容
    var v1 = $(this).text();
    // 创建input标签,并且内部设置值
    var inp = document.createElement('input');
    $(inp).val(v1);
    // 添加到td标签中
    $(this).html(inp);
    }
    })
    }

    // 退出编辑模式
    function trOutEdit($tr) {
    $tr.find('td[edit-enable="true"]').each(function () {
    // $(this) 每一个td
    var editType = $(this).attr('edit-type'); // 获得标签类型
    if(editType == 'select'){
    var option = $(this).find('select')[0].selectedOptions; // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签
    $(this).html($(option).text());
    }else {
    var inputVal = $(this).find('input').val(); // 获得tr标签中所有input标签的值
    $(this).html(inputVal); // 为当前td标签添加html格式内容
    }
    })

    }

    jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用
    xx: function (url) {
    // {#通过ajax异步请求获得初始化数据#}
    initial(url)

    // 通过js控制,控制标签类型,完成进入编辑模式功能
    $('#tbBody').on('click', ':checkbox', function () { // 在tbBody标签范围中为所有checkbox添加click事件
    // 检测多选框是否已经被选中
    var $tr = $(this).parent().parent() // 通过checkbox标签获得tr标签中的元素
    if ($(this).prop('checked')){ // prop()获得标签属性值
    // 进入编辑模式
    trIntoEdit($tr);
    }else {
    // 退出编辑模式
    trOutEdit($tr);
    }
    })
    }
    })
    })(jQuery) // 传入jQeury对象

    十五、增删改查插件之全选反选取消以及进入编辑模式按钮
    curd.html

    # 略……
    <div style=" 700px;margin: 0 auto">
    <h1>服务器列表</h1>
    <div class="btn-group" role="group" aria-label="...">
    <button id="checkAll" type="button" class="btn btn-default">全选</button>
    <button id="checkReverse" type="button" class="btn btn-default">反选</button>
    <button id="checkCancel" type="button" class="btn btn-default">取消</button>
    <button id="inOutEditMode" type="button" class="btn btn-default">进入编辑模式</button>
    <a class="btn btn-default" href="#">添加</a>
    <button id="multiDel" type="button" class="btn btn-default">删除</button>
    <button id="save" type="button" class="btn btn-default">保存</button>
    </div>
    <table class="table table-bordered table-striped">
    <thead id="tbHead">
    <tr>

    </tr>
    </thead>
    <tbody id="tbBody">
    </tbody>
    </table>
    # 略……

    views:

    # 略……
    // 为所有按钮绑定事件
    // 为全选按钮绑定事件
    $('#checkAll').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){ // 是否进入了编辑模式
    $('#tbBody').find(':checkbox').each(function () {
    if(!$(this).prop('checked')){ // 将没有被选中的一起选中
    var $tr = $(this).parent().parent();
    trIntoEdit($tr); // 进入编辑状态
    $(this).prop('checked', true) // 多选框被选中状态
    }else {
    $(this).prop('checked',true);
    }
    })
    }else {
    $('#tbBody').find(':checkbox').prop('checked', true) // 未进入编辑模式,所有不变
    }

    })

    // 为反选按钮绑定事件
    $('#checkReverse').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){ // 进入编辑模式
    $('#tbBody').find(':checkbox').each(function () {
    var $tr = $(this).parent().parent();
    if ($(this).prop('checked')){
    trOutEdit($tr); // 退出编辑状态
    $(this).prop('checked', false)
    }else {
    trIntoEdit($tr);
    $(this).prop('checked', true);
    }
    })
    }else {
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 如果是选中状态
    $(this).prop('checked', false); // 修改为未选中状态
    }else {
    $(this).prop('checked',true);
    }
    })
    }
    })

    // 为取消按钮绑定事件
    $('#checkCancel').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){
    $('#tbBody').find(':checkbox').each(function () {
    if($(this).prop('checked')){
    var $tr = $(this).parent().parent();
    trOutEdit($tr);
    }
    $(this).prop('checked', false);
    });
    }else {
    $('#tbBody').find(':checkbox').prop('checked', false)
    }
    })

    // 为编辑按钮绑定事件
    $('#inOutEditMode').click(function () {
    if ($(this).hasClass('btn-warning')){
    // 需要退出编辑模式时
    $(this).removeClass('btn-warning');
    $(this).text('进入编辑模式');
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 如果是可编辑状态
    var $tr = $(this).parent().parent();
    trOutEdit($tr); // 退出编辑状态
    }
    })
    }else {
    // 进入编辑模式
    $(this).addClass('btn-warning');
    $(this).text('退出编辑模式');
    $('#tbBody').find(':checkbox').each(function(){
    if($(this).prop('checked')){
    var $tr = $(this).parent().parent();
    trIntoEdit($tr);
    }
    });
    }
    })
    #略……

    十六、增删改查插件之批量删除数据
    nb-list.js 自定义文件

    // 批量删除按钮绑定事件
    $('#multiDel').click(function () {
    var idList=[];
    // 查找所有属性值为checked的标签多选框
    $('#tbBody').find(':checked').each(function () {
    var v = $(this).val();
    idList.push(v);
    });
    $.ajax({
    url:url,
    type:'delete',
    data:JSON.stringify(idList), // 将列表转换成json字符发送给后台
    sucess:function (arg) {
    console.log(arg)

    }

    })
    });

    views:

    # 采用restful(面向资源编程)风格
    def curd_json(request):
    """
    ajax请求方法
    :param request:
    :return:
    """
    if request.method == 'DELETE':
    id_list = json.loads(str(request.body, encoding='utf-8')) # 需要从body请求体中取出数据
    print(id_list)
    return HttpResponse('--')
    elif request.method == 'POST':
    pass
    elif request.method == 'GET':

    十八、增删改查插件之批量更新
    nb-list.js

    // {#生成表格数据信息#}
    function initTableBody(serverList, tableConfig) {
    $('#tbBody').empty();
    $.each(serverList, function (k, row) { // 循环查询出来数据表中所有的数据
    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */
    var tr = document.createElement('tr')
    tr.setAttribute('nid',row.id); // 生成每一行数据的id值放入标签属性中

    // 退出编辑模式
    function trOutEdit($tr) {
    # 略……
    var option = $(this).find('select')[0].selectedOptions; // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签
    $(this).attr('new-origin', $(option).val()); // 将修改的值放入标签属性中
    $(this).html($(option).text());
    # 略……

    // 保存按钮绑定事件
    $('#save').click(function () {
    // 进入编辑模式
    if ($('#inOutEditMode').hasClass('btn-warning')){
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 获得处于被选中状态下的标签
    var $tr = $(this).parent().parent()
    trOutEdit($tr); // 退出编辑模式
    }
    });
    };

    var all_list = []
    // 获取用户修改过的数据
    $('#tbBody').children().each(function () { // 获得每一个tr标签
    // $(this) = tr
    var $tr = $(this);
    var row_dict = {};
    var flag = false;
    var nid = $tr.attr('nid');

    $tr.children().each(function () { // 获得每一个td标签
    if ($(this).attr('edit-enable')){ // 属于可编辑的标签
    if($(this).attr('edit-type') == 'select'){// td标签属于select下拉框时
    var newData = $(this).attr('new-origin'); // 获得修改后的值
    var oldData = $(this).attr('origin');
    if (newData){
    if (newData != oldData){
    var name = $(this).attr('name')
    row_dict[name] = newData;
    flag = true;
    }
    }
    }else { // td标签属于input框时
    var newData = $(this).text();
    var oldData = $(this).attr('origin');
    console.log(newData, oldData)
    if (newData != oldData){
    var name =$(this).attr('name'); // 获得字段名称
    row_dict[name] = newData; // 封装成字典格式数据,便于数据库查询
    flag = true;
    }
    }
    }
    });
    if(flag){
    row_dict['id'] = nid; // 获得该条数据的id
    }
    all_list.push(row_dict); // 往数据库插入数据时需要用到的字典列表
    });
    // 通过Ajax提交后台
    $.ajax({
    url:url,
    type:'PUT',
    data:JSON.stringify(all_list),
    sucess:function (arg) {
    console.log(arg)

    }
    })
    });

    views:

    def curd_json(request):
    """
    ajax请求方法
    :param request:
    :return:
    """
    if request.method == 'DELETE':
    id_list = json.loads(str(request.body, encoding='utf-8')) # 需要从body请求体中取出数据
    print(id_list)
    return HttpResponse('--')
    elif request.method == 'POST':
    pass
    elif request.method == 'PUT':
    all_list = json.loads((str(request.body, encoding='utf-8'))) # 编码成字符串
    print(all_list)
    return HttpResponse('---')
    elif request.method == 'GET':
    table_config = [ # 配置文件,用于前端页面数据定制显示
    # 生成checkbox多选框字段
    {
    'q': None, # 不作为数据库查询字段
    'title': '选择',
    'display': True,
    'text': {
    'tpl': "<input type='checkbox' value='{n1}' />",
    'kwargs': {'n1': '@id', }
    },
    'attrs': {'nid': '@id'}
    },

    # 生成id字段
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'display': False, # display表示该字段在前端页面表格表头是否显示
    'text': { # text用来将数据库中取出的值进行字符串格式化
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符模板
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    },
    'attrs': {'k1': 'v1', 'k2': '@hostname'} # 为前端标签添加属性及属性值
    },
    {
    'q': 'hostname',
    'title': '主机名',
    'display': True,
    'text': {
    'tpl': '{n1}-{n2}',
    'kwargs': {'n1': '@hostname', 'n2': '@id'}
    },
    'attrs': {'edit-enable': 'true', 'k2': '@hostname', 'origin': '@hostname', 'name': 'hostname'}
    # edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q': None,
    'title': '操作',
    'display': True,
    'text': {
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs': {'nid': '@id'},
    },
    'attrs': {'k1': 'v1', 'k2': '@hostname'}
    },
    ]

    # 组装数据库查询所需的字段
    value_list = []
    for row in table_config:
    if not row['q']:
    continue
    value_list.append(row['q'])

    server_list = models.Server.objects.values(*value_list) # 传入列表获得字典格式数据
    ret = {
    'server_list': list(server_list), # 将Querylist转换成列表
    'table_config': table_config,
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    十九、 增删改查插件之快速实现IDC基本增删改查
    urls:

    # idc机房信息展示
    url(r'^idc.html$', views.idc),
    url(r'^idc_json.html$', views.idc_json),

    views:


    def idc(request):
    """
    跳转到idc页面
    :param request:
    :return:
    """
    return render(request, 'idc.html')

    def idc_json(request):
    if request.method == 'DELETE':
    id_list = json.loads(str(request.body, encoding='utf-8'))
    print(id_list)
    return HttpResponse('删除成功')
    elif request.method == 'PUT':
    all_list = json.loads(str(request.body, encoding='utf-8'))
    print(all_list)
    return HttpResponse('保存成功')
    elif request.method == 'GET':
    from backend.page_config import idc
    values_list = []
    for row in idc.table_config: # 从配置文件中获取数据库查询所需的字段
    if not row['q']:
    continue
    values_list.append(row['q'])
    server_list = models.IDC.objects.values(*values_list)

    ret={
    'server_list':list(server_list),
    'table_config':idc.table_config,
    'global_dict':{}
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))
    page_config:

    table_config = [ # 配置文件,用于前端页面数据定制显示
    # 生成checkbox多选框字段
    {
    'q': None, # 不作为数据库查询字段
    'title': '选择',
    'display': True,
    'text': {
    'tpl': "<input type='checkbox' value='{n1}' />",
    'kwargs': {'n1': '@id', }
    },
    'attrs': {'nid': '@id'}
    },

    # 生成id字段
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'display': False, # display表示该字段在前端页面表格表头是否显示
    'text': { # text用来将数据库中取出的值进行字符串格式化
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符模板
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    },
    'attrs': {'k1': 'v1', 'k2': '@id'} # 为前端标签添加属性及属性值
    },
    {
    'q': 'name',
    'title': '机房名',
    'display': True,
    'text': {
    'tpl': '{n1}',
    'kwargs': {'n1': '@name',}
    },
    'attrs':{'edit-enable':'true','origin':'@name','name':'name'}
    # edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q': 'floor',
    'title': '楼层',
    'display': True,
    'text': {
    'tpl': "{n1}",
    'kwargs': {'n1': '@floor'},
    },
    'attrs':{'edit-enable':'true','origin':'@floor','name':'floor'}
    },
    ]

    html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
    </head>
    <body>
    <div style=" 700px; margin: 0 auto">
    <h1>IDC列表</h1>
    {% include 'nb-tpl.html' %}
    </div>

    <script src="/static/jquery-1.12.4.js"></script>
    <script src = "/static/nb-list.js"></script>
    <script>
    {#调用自定义的jQuery函数#}
    $.xx('/backend/curd_json.html');
    </script>
    </body>
    </html>

    nb-tpl.html页面组件:

    <div class="btn-group" role="group" aria-label="...">
    <button id='checkAll' type="button" class="btn btn-default">全选</button>
    <button id='checkReverse' type="button" class="btn btn-default">反选</button>
    <button id='checkCancel' type="button" class="btn btn-default">取消</button>
    <button id='inOutEditMode' type="button" class="btn btn-default">进入编辑模式</button>
    <a class="btn btn-default" href="#" role="button">添加</a>
    <button id='multiDel' type="button" class="btn btn-default">删除</button>
    <button id='refresh' type="button" class="btn btn-default">刷新</button>
    <button id='save' type="button" class="btn btn-default">保存</button>
    </div>
    <table class="table table-bordered table-striped">
    <thead id="tbHead">

    </thead>
    <tbody id="tbBody">

    </tbody>

    </table>

    nb-list.js 自定义js文件:

    // 自定义js匿名函数,属于自动调用,只有内部调用,防止当成插件时的同名冲突
    (function (jq) {
    var GLOBAL_DICT={};
    /*
    {
    'device_type_choices': (
    (1, '服务器'),
    (2, '交换机'),
    (3, '防火墙'),
    )
    'device_status_choices': (
    (1, '上架'),
    (2, '在线'),
    (3, '离线'),
    (4, '下架'),
    )
    }
    */


    // 为字符串创建format方法,用于字符串格式化
    String.prototype.format = function (args) {
    return this.replace(/{(w+)}/g, function (s, i) {
    return args[i];
    });
    };

    // {#页面加载时自动发送ajax请求#}
    function initial(url) {
    $.ajax({
    url: url,
    type: 'GET',
    // {#将响应的字符串数据转换成字典格式#}
    dataType: 'JSON',
    success: function (arg) {
    // 将 (1, '服务器')……等数据作成全局常量
    $.each(arg.global_dict, function (k ,v) {
    GLOBAL_DICT[k] = v
    });
    // {#生成表头字段#}
    initTableHeader(arg.table_config);
    // {#生成表格数据#}
    initTableBody(arg.server_list, arg.table_config);
    }
    })
    }

    // {#生成表头字段#}
    function initTableHeader(tableConfig) {
    $('#tbHead').empty() // 清除该标签内的所有内容
    var tr = document.createElement('tr') // 生成tr标签
    // {#循环生成字段表头#}
    $.each(tableConfig, function (k, v) {
    if (v.display) { // 为Ture时需要展示
    var tag = document.createElement('th');
    tag.innerHTML = v.title
    $('#tbHead').find('tr').append(tag);
    }
    })
    }

    // {#生成表格数据信息#}
    function initTableBody(serverList, tableConfig) {
    $('#tbBody').empty();
    $.each(serverList, function (k, row) { // 循环查询出来数据表中所有的数据
    // row: {'id': 1, 'hostname':c2.com, create_at: xxxx-xx-xx-}
    /*
    <tr>
    <td>id</td>
    <td>hostn</td>
    <td>create</td>
    </tr>
    */
    var tr = document.createElement('tr')
    tr.setAttribute('nid',row.id);
    $.each(tableConfig, function (kk, rrow) {
    if (rrow.display) { // 是否需要展示该字段对应的内容
    // kk: 1 rrow:{'q':'id','title':'ID'}, // rrow.q = "id"
    // kk: . rrow:{'q':'hostname','title':'主机名'},// rrow.q = "hostname"
    // kk: . rrow:{'q':'create_at','title':'创建时间'}, // rrow.q = "create_at"
    var td = document.createElement('td');
    // rrow['q']
    // rrow['text']
    // rrow.text.tpl = "asdf{n1}sdf"
    // rrow.text.kwargs = {'n1':'@id','n2':'@@123'}
    var newKwargs = {}; // {'n1':'1','n2':'123'}
    $.each(rrow.text.kwargs, function (kkk, vvv) { // 循环字典
    var av = vvv;
    if(vvv.substring(0,2) == '@@'){ // 生成数字对应的字符串值
    var global_dict_key = vvv.substring(2, vvv.length); // 获得数据表中的字段名 例device_type_choices
    var nid = row[rrow.q] // 通过自定义的配置字典,获得数据表中该条数据的id值
    $.each(GLOBAL_DICT[global_dict_key], function (gk, gv) {
    if(gv[0] == nid){
    av = gv[1]; // av = '服务器'
    }
    })
    }
    // {#@表示需要进行字符串格式化#}
    else if (vvv[0] == '@') {
    // {#进行切分,获得@后面的具体字段名,用于从数据库中取出具体的值 #}
    av = row[vvv.substring(1, vvv.length)];
    }
    newKwargs[kkk] = av;
    });

    // {#通过自定义的扩展方法进行字符串格式化#}
    var newText = rrow.text.tpl.format(newKwargs);
    td.innerHTML = newText;

    // 在标签中添加属性
    $.each(rrow.attrs, function (atkey, atval) {
    // 如果@
    if(atval[0] == '@'){
    td.setAttribute(atkey, row[atval.substring(1, atval.length)]);
    }else {
    td.setAttribute(atkey, atval);
    }
    });

    $(tr).append(td)
    }
    });
    $('#tbBody').append(tr);
    })
    }

    // 进入编辑模式
    function trIntoEdit($tr) {
    if ($('#inOutEditMode').hasClass('btn-warning')){ // 是否进入了编辑模式
    $tr.find('td[edit-enable="true"]').each(function () { // 找到tr标签下所有td标签中属性为edit-enable=true的元素,并循环它
    // $(this) 每一个td标签
    var editType = $(this).attr('edit-type'); // 从配置列表中获得编辑类型的值
    if(editType == 'select'){
    // 生成下拉框,找到数据源
    var deviceTypeChoices = GLOBAL_DICT[$(this).attr('global_key')];
    // 生成select下拉框标签
    var selectTag = document.createElement('select');
    var origin = $(this).attr('origin'); // 获得当前标签中的origin属性的值
    $.each(deviceTypeChoices, function (k, v) { // v的值为 (1, '服务器'),
    var option = document.createElement('option');
    $(option).text(v[1]); // 为option标签添加文本值
    $(option).val(v[0]); // 为option标签添加属性值
    if(v[0] == origin){
    // 默认选中原来的值
    $(option).prop('selected', true);
    }
    $(selectTag).append(option);
    });
    $(this).html(selectTag)
    }else {
    // 获取原来td中的文本内容
    var v1 = $(this).text();
    // 创建input标签,并且内部设置值
    var inp = document.createElement('input');
    $(inp).val(v1);
    // 添加到td标签中
    $(this).html(inp);
    }
    })
    }
    }

    // 退出编辑模式
    function trOutEdit($tr) {
    $tr.find('td[edit-enable="true"]').each(function () {
    // $(this) 每一个td
    var editType = $(this).attr('edit-type'); // 获得标签类型
    if(editType == 'select'){
    var option = $(this).find('select')[0].selectedOptions; // 将jquery对象转换为DOM对象,调用selectOptions获得select标签中的options标签
    $(this).attr('new-origin', $(option).val()); // 将修改的值放入标签属性中
    $(this).html($(option).text());
    }else {
    var inputVal = $(this).find('input').val(); // 获得tr标签中所有input标签的值
    $(this).html(inputVal); // 为当前td标签添加html格式内容
    }
    })

    }

    jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用
    xx: function (url) {
    // {#通过ajax异步请求获得初始化数据#}
    initial(url)

    // 通过js控制,控制标签类型,完成进入编辑模式功能
    // 在tbBody标签范围中为所有checkbox添加click事件
    $('#tbBody').on('click', ':checkbox', function () {
    // 检测多选框是否已经被选中
    var $tr = $(this).parent().parent() // 通过checkbox标签获得tr标签中的元素
    if ($(this).prop('checked')){ // prop()获得标签属性值
    // 进入编辑模式
    trIntoEdit($tr);
    }else {
    // 退出编辑模式
    trOutEdit($tr);
    }
    });

    // 为所有按钮绑定事件
    // 为全选按钮绑定事件
    $('#checkAll').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){ // 是否进入了编辑模式
    $('#tbBody').find(':checkbox').each(function () {
    if(!$(this).prop('checked')){ // 将没有被选中的一起选中
    var $tr = $(this).parent().parent();
    trIntoEdit($tr); // 进入编辑状态
    $(this).prop('checked', true) // 多选框被选中状态
    }else {
    $(this).prop('checked',true);
    }
    })
    }else {
    $('#tbBody').find(':checkbox').prop('checked', true) // 未进入编辑模式,所有不变
    }

    })

    // 为反选按钮绑定事件
    $('#checkReverse').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){ // 进入编辑模式
    $('#tbBody').find(':checkbox').each(function () {
    var $tr = $(this).parent().parent();
    if ($(this).prop('checked')){
    trOutEdit($tr); // 退出编辑状态
    $(this).prop('checked', false)
    }else {
    trIntoEdit($tr);
    $(this).prop('checked', true);
    }
    })
    }else {
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 如果是选中状态
    $(this).prop('checked', false); // 修改为未选中状态
    }else {
    $(this).prop('checked',true);
    }
    })
    }
    })

    // 为取消按钮绑定事件
    $('#checkCancel').click(function () {
    if($('#inOutEditMode').hasClass('btn-warning')){
    $('#tbBody').find(':checkbox').each(function () {
    if($(this).prop('checked')){
    var $tr = $(this).parent().parent();
    trOutEdit($tr);
    }
    $(this).prop('checked', false);
    });
    }else {
    $('#tbBody').find(':checkbox').prop('checked', false)
    }
    })

    // 为编辑按钮绑定事件
    $('#inOutEditMode').click(function () {
    if ($(this).hasClass('btn-warning')){
    // 需要退出编辑模式时
    $(this).removeClass('btn-warning');
    $(this).text('进入编辑模式');
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 如果是可编辑状态
    var $tr = $(this).parent().parent();
    trOutEdit($tr); // 退出编辑状态
    }
    })
    }else {
    // 进入编辑模式
    $(this).addClass('btn-warning');
    $(this).text('退出编辑模式');
    $('#tbBody').find(':checkbox').each(function(){
    if($(this).prop('checked')){
    var $tr = $(this).parent().parent();
    trIntoEdit($tr);
    }
    });
    }
    })

    // 批量删除按钮绑定事件
    $('#multiDel').click(function () {
    var idList=[];
    // 查找所有属性值为checked的标签多选框
    $('#tbBody').find(':checked').each(function () {
    var v = $(this).val();
    idList.push(v);
    });
    $.ajax({
    url:url,
    type:'DELETE',
    data:JSON.stringify(idList), // 将列表转换成json字符发送给后台
    sucess:function (arg) {
    console.log(arg)

    }

    })
    });

    // 刷新页面按钮绑定事件
    $('#refresh').click(function () {
    initial(url);
    })

    // 保存按钮绑定事件
    $('#save').click(function () {
    // 进入编辑模式
    if ($('#inOutEditMode').hasClass('btn-warning')){
    $('#tbBody').find(':checkbox').each(function () {
    if ($(this).prop('checked')){ // 获得处于被选中状态下的标签
    var $tr = $(this).parent().parent()
    trOutEdit($tr); // 编辑模式
    }
    });
    };
    var all_list = []
    // 获取用户修改过的数据
    $('#tbBody').children().each(function () { // 获得每一个tr标签
    // $(this) = tr
    var $tr = $(this);
    var row_dict = {};
    var flag = false;
    var nid = $tr.attr('nid');

    $tr.children().each(function () { // 获得每一个td标签
    if ($(this).attr('edit-enable')){ // 属于可编辑的标签
    if($(this).attr('edit-type') == 'select'){// td标签属于select下拉框时
    var newData = $(this).attr('new-origin');
    var oldData = $(this).attr('origin');
    if (newData){
    if (newData != oldData){
    var name = $(this).attr('name')
    row_dict[name] = newData;
    flag = true;
    }
    }
    }else { // td标签属于input框时
    var newData = $(this).text();
    var oldData = $(this).attr('origin');
    console.log(newData, oldData)
    if (newData != oldData){
    var name =$(this).attr('name'); // 获得字段名称
    row_dict[name] = newData; // 封装成字典格式数据,便于数据库查询
    flag = true;
    }
    }
    }
    });
    if(flag){
    row_dict['id'] = nid; // 获得该条数据的id
    }
    all_list.push(row_dict); // 往数据库插入数据时需要用到的字典列表
    });
    // 通过Ajax提交后台
    $.ajax({
    url:url,
    type:'PUT',
    data:JSON.stringify(all_list),
    sucess:function (arg) {
    console.log(arg)

    }
    })
    });

    }
    });
    })(jQuery) // 传入jQeury对象

    二十、 CMDB搜索功能
    nb-search.html 自定义搜索组件:

    <div class="search-list clearfix" style="position: relative;">
    {# 搜索框#}
    <div class="search-btn col-md-offset-10 col-md-2" style="position: absolute ;bottom:1px;text-align: right ">
    <input id="doSearch" type="button" value="搜索" class="btn btn-primary">
    </div>

    {# 加号按钮+下拉框 +输入框 #}
    <div class="search-item col-md-10 clearfix" style="position: relative;height: 35px" >
    {# 加号部分#}
    <div style="position: absolute;left: 0;">
    <a class="btn btn-default add-search-condition" >
    <span class="glyphicon glyphicon-plus"></span>
    </a>
    </div>
    {# 下拉框+输入框部分#}
    <div class="input-group searchArea" style="position: absolute; left: 40px;">
    <div class="input-group-btn">
    <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    <span class="searchDefault">默认值</span>
    <span class="caret"></span>
    </button>
    <ul class="dropdown-menu">

    </ul>
    </div>
    {# 搜索输入框#}
    {# <input type="text" class="form-control" aria-label="..." >#}
    </div>
    </div>
    </div>

    nb-list.js 自定义js文件:


    var CREATE_SEARCH_CONDITION = true; // 用来控制点击搜索后,保留搜索框中的内容

    function getSearchCondition() {
    var condition={};
    $('.search-list').find('input[type="text"], select').each(function () {
    var name = $(this).attr('name');
    var value = $(this).val();
    // 组装成字典发送到后台进行数据库查询
    if (condition[name]){ // 如果存在相同的属性名,则组装成列表
    condition[name].push(value); // 为列表添加值时采用push()
    }else {
    condition[name] = [value]; // 组装成字典,值为列表格式
    }
    });

    return condition;
    }

    // {#页面加载时自动发送ajax请求#}
    function initial(url) {
    // 执行一个函数,获取当前搜索条件
    var searchCondition = getSearchCondition();

    $.ajax({
    url: url,
    type: 'GET',
    // {#将响应的字符串数据转换成字典格式#}
    dataType: 'JSON',
    data:{condition: JSON.stringify(searchCondition)},
    success: function (arg) {
    // 将 (1, '服务器')……等数据作成全局常量
    $.each(arg.global_dict, function (k ,v) {
    GLOBAL_DICT[k] = v
    });
    // {#生成表头字段#}
    initTableHeader(arg.table_config);
    // {#生成表格数据#}
    initTableBody(arg.server_list, arg.table_config);
    // 初始化搜索条件
    initSearch(arg.search_config)
    }
    })
    }


    // 初始化搜索条件类型
    function initSearch(searchConfig) {
    if (searchConfig && CREATE_SEARCH_CONDITION){
    CREATE_SEARCH_CONDITION = false;
    // 生成搜索类型的下拉框
    $.each(searchConfig, function (k,v) {
    var li = document.createElement('li');
    $(li).attr('search_type', v.search_type);
    $(li).attr('name', v.name);
    if (v.search_type == 'select'){
    // 生成下拉框时所需的属性值
    $(li).attr('global_name', v.global_name);
    }
    var a = document.createElement('a')
    // 搜索项名称
    a.innerHTML = v.text;
    $(li).append(a);
    $('.searchArea ul').append(li);
    });
    // 初始化默认搜索条件
    // searchConfig[0]为初始值
    // 初始化默认选中值
    $('.search-item .searchDefault').text(searchConfig[0].text);

    // 生成默认搜索内容框模块
    if(searchConfig[0].search_type == 'select'){ // 内容输入框变为下拉框
    var sel = document.createElement('select');
    $(sel).attr('class','form-control');
    $.each(GLOBAL_DICT[searchConfig[0].global_name], function (k,v) {
    var op = document.createElement('option');
    $(op).text(v[1]); // (1,主机1),(2,主机2)……
    $(op).val(v[0]);
    $(sel).append(op);
    })
    $('.input-group').append(sel);
    }else { // 内容输入框变为input框
    var inp = document.createElement('input')
    $(inp).attr('name', searchConfig[0].name);
    $(inp).attr('type', 'text');
    $(inp).attr('class', 'form-control');
    $('.input-group').append(inp);
    }

    }
    }


    jq.extend({ // 通过jQuery继承函数xx,可以直接通过$.xx(url)来直接进行调用
    xx: function (url) {
    // {#通过ajax异步请求获得初始化数据#}
    initial(url)

    // 点击不同的搜索类型生成不同的搜索形式(input 或者 select)
    $('.search-list').on('click', 'li', function () {
    var wenben = $(this).text();
    var searchType = $(this).attr('search_type');
    var name = $(this).attr('name');
    var globalName = $(this).attr('global_name');
    // 把显示替换 prev()获得同胞元素
    $(this).parent().prev().find('.searchDefault').text(wenben);

    if(searchType == 'select'){
    /*
    [
    [1,‘文本’],
    [1,‘文本’],
    [1,‘文本’],
    ]
    */
    // 组装搜索内容为下拉框
    var sel = document.createElement('select');
    $(sel).attr('class','form-control');
    $(sel).attr('name',name);
    $.each(GLOBAL_DICT[globalName],function(k,v){
    var op = document.createElement('option');
    $(op).text(v[1]);
    $(op).val(v[0]);
    $(sel).append(op);
    });
    $(this).parent().parent().next().remove(); // 移除原有的搜索输入框
    $(this).parent().parent().after(sel); // 将新的搜索内容下拉框添加到
    }else {
    // 搜索内容类型为input框
    var inp = document.createElement('input');
    $(inp).attr('name', name);
    $(inp).attr('type', 'text');
    $(inp).attr('class', 'form-control');

    $(this).parent().parent().next().remove();
    $(this).parent().parent().after(inp);
    }
    });

    // 拷贝新的搜索项
    $('.search-list').on('click', '.add-search-condition', function () {
    var newSearchItem = $(this).parent().parent().clone(); // 获得整个搜索项
    $(newSearchItem).find('.add-search-condition span').removeClass('glyphicon-plus').addClass('glyphicon-minus');
    $(newSearchItem).find('.add-search-condition').addClass('del-search-condition').removeClass('add-search-condition');
    $('.search-list').append(newSearchItem);
    });

    // 删除搜索项
    $('.search-list').on('click','.del-search-condition',function(){
    $(this).parent().parent().remove();
    });

    // 搜索按钮绑定事件
    $('#doSearch').click(function () {
    initial(url); // 重新加载页面
    })

    }
    });
    })(jQuery) // 传入jQeury对象

    views:


    def get_data_list(request,model_cls,table_config):
    """
    根据搜索条件进行查询
    :param request:
    :param model_cls:
    :param table_config:
    :return:
    """
    values_list = []
    for row in table_config:
    if not row['q']:
    continue
    values_list.append(row['q'])

    from django.db.models import Q

    condition = request.GET.get('condition')
    condition_dict = json.loads(str(condition))

    con = Q()
    for name,values in condition_dict.items(): # {'hostname__contains': ['c1', 'c2']}
    ele = Q() # select xx from where cabinet_num=sdf or cabinet_num='123'
    ele.connector = 'OR'
    for item in values: # ['c1', 'c2']
    ele.children.append((name,item))
    con.add(ele, 'AND') # (AND: (OR: ('hostname__contains', 'c1'), ('hostname__contains', 'c2')))

    server_list = model_cls.objects.filter(con).values(*values_list)
    return server_list


    def curd(request):
    """
    进入到curd.html页面
    :param request:
    :return:
    """
    return render(request, 'curd.html')


    def curd_json(request):
    """
    ajax请求方法
    :param request:
    :return:
    """
    if request.method == 'DELETE':
    id_list = json.loads(str(request.body, encoding='utf-8')) # 需要从body请求体中取出数据
    print(id_list)
    return HttpResponse('--')
    elif request.method == 'POST':
    pass
    elif request.method == 'PUT':
    all_list = json.loads((str(request.body, encoding='utf-8'))) # 编码成字符串
    print(all_list)
    return HttpResponse('---')
    elif request.method == 'GET':
    from backend.page_config import curd as curdConfig
    server_list = get_data_list(request, models.Server, curdConfig.table_config)

    ret = {
    'server_list': list(server_list), # 将Querylist转换成列表
    'table_config': curdConfig.table_config,
    'search_config': curdConfig.search_config,
    'global_dict':{ # 用于生成下拉框
    'device_type_choices':models.Asset.device_type_choices,
    'device_status_choices':models.Asset.device_status_choices,
    }
    }
    return HttpResponse(json.dumps(ret, cls=JsonCustomEncoder))

    curd.py 配置文件内容:

    table_config = [ # 配置文件,用于前端页面数据定制显示
    # 生成checkbox多选框字段
    {
    'q': None, # 不作为数据库查询字段
    'title': '选择',
    'display': True,
    'text': {
    'tpl': "<input type='checkbox' value='{n1}' />",
    'kwargs': {'n1': '@id', }
    },
    'attrs': {'nid': '@id'}
    },

    # 生成id字段
    {
    'q': 'id', # 用于数据库查询字段名
    'title': 'ID', # 用于前端页面中表头字段名的显示
    'display': False, # display表示该字段在前端页面表格表头是否显示
    'text': { # text用来将数据库中取出的值进行字符串格式化
    'tpl': '{n1}', # 用于生成格式化字符串中的占位符模板
    'kwargs': {'n1': '@id'} # 占位符中具体的id数值,用于生成链接中对单条数据的操作
    },
    'attrs': {'k1': 'v1', 'k2': '@hostname'} # 为前端标签添加属性及属性值
    },
    {
    'q': 'hostname',
    'title': '主机名',
    'display': True,
    'text': {
    'tpl': '{n1}-{n2}',
    'kwargs': {'n1': '@hostname', 'n2': '@id'}
    },
    'attrs': {'edit-enable': 'true', 'k2': '@hostname', 'origin': '@hostname', 'name': 'hostname'}
    # edit-enable允许编辑, k2表示字段当前值,用于进行值的前后对比完成值的修改
    },

    # 页面显示 操作: 删除,编辑,a标签生成
    {
    'q': None,
    'title': '操作',
    'display': True,
    'text': {
    'tpl': "<a href='/del?nid={nid}'>删除</a>",
    'kwargs': {'nid': '@id'},
    },
    'attrs': {'k1': 'v1', 'k2': '@hostname'}
    },

    ]

    # 搜索框部分所需的配置
    # hostname__contains用于实现模糊查询
    search_config=[
    {'name':'hostname__contains', 'text':'主机名','search_type':'input'},
    # {'name':'sn__contains', 'text':'SN号','search_type':'input'},
    ]

    二十一、HightCharts制图框架
    urls:

    url(r'^chart.html$', views.chart ),
    1
    views:

    def chart(request):
    """
    跳转到页面
    :param request:
    :return:
    """
    return render(request, 'chart.html')

    chart.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>

    <div id="i1"></div>


    <script src="/static/jquery-1.12.4.js"></script>
    <script src="/static/Highcharts-5.0.12/code/highcharts.js"></script>
    <script>
    // jQuery == $
    Highcharts.setOptions({
    global: {
    useUTC: false // 不使用UTC时区
    }
    });

    var chart = new Highcharts.Chart('i1', {
    title: {
    text: '大标题',
    x: 0
    },

    subtitle: {
    text: '数据来源: WorldClimate.com',
    x: 0
    },

    chart: {
    events: {
    load: function (e) {
    // 图标加载时,执行的函数
    console.log('图标加载时,执行的函数')
    }
    }
    },

    credits: {
    enable: true,
    position: {
    align: 'right',
    verticalAlign: 'bottom'
    },
    text: 'oldboy',
    href: 'http://www.oldboyedu.com'
    },
    xAxis: {
    // 适用于固定x轴
    type: 'datetime',
    labels: {
    formatter: function () {
    return Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.value);
    },
    rotation: 30
    }
    },
    yAxis: {
    title: {
    text: '数值'
    }
    },
    tooltip: { // 工具提示
    pointFormatter: function (e) {
    var tpl = '<span style="color:' + this.series.color + '">哦哦哦哦哦小</span> ' + this.series.name + ': <b>' + this.y + '个</b><br/>';
    return tpl;
    },
    useHTML: true
    },

    plotOptions: {
    series: { // 系列数据
    cursor: 'pointer',
    events: {
    click: function (event) {
    // 点击某个指定点时,执行的事件
    console.log(this.name, event.point.x, event.point.y);
    }
    }
    }
    },

    series: [
    {
    name: '洛杉矶',
    data: [
    [1501689804077.358, 8.0],
    [1501689814177.358, 6.9],
    [1501689824277.358, 16.9],
    [1501689834377.358, 11.9]
    ]
    },
    {
    name: '南京',
    data: [
    [1501689804077.358, 18.0],
    [1501689814177.358, 16.9],
    [1501689824277.358, 6.9],
    [1501689834377.358, 21.9]
    ]
    }
    ]
    });

    </script>
    </body>
    </html>

    十三、Restful 面向资源编程,实现接口开发
    urls:

    # restful面向资源编程
    url(r'^servers.html$', views.servers),
    url(r'^servers/(d+).html$', views.servers_detail),

    views:


    from django.http import JsonResponse
    def servers(request):
    # http://127.0.0.1:8000/api/servers.html GET: 获取服务器列表
    # http://127.0.0.1:8000/api/servers.html POST: 创建服务器
    # http://127.0.0.1:8000/api/servers/1.html GET: 获取单条信息
    # http://127.0.0.1:8000/api/servers/1.html DELETE: 删除单条信息
    # http://127.0.0.1:8000/api/servers/1.html PUT: 更新
    if request.method == 'GET':
    v = models.Server.objects.values('id', 'hostname')
    server_list = list(v)
    return JsonResponse(server_list, safe=False) # 默认只能发送字典格式数据
    elif request.method == 'POST':
    return JsonResponse(status=201)

    def servers_detail(request,nid):
    """
    获得单条数据的操作
    :param request:
    :param nid:
    :return:
    """
    if request.method == 'GET':
    obj = models.Server.objects.filter(id=nid).first()
    return HttpResponse('...')
    elif request.method == "DELETE":
    models.Server.objects.filter(id=nid).delete()
    return HttpResponse()
    elif request.method == 'PUT':
    request.body
    models.Server.objects.filter(id=nid).update()

  • 相关阅读:
    python cookbook 笔记二
    python cookbook 笔记一
    aircrack-ng笔记
    TeamCity 和 Nexus 的使用
    Linux 搭建 nexus 私服【转】
    maven阿里云镜像
    kali linux 破解wpa密码
    python正则表达式二[转]
    Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
    集合解析
  • 原文地址:https://www.cnblogs.com/jokezl/p/10472171.html
Copyright © 2020-2023  润新知