Django 使用xadmin定制简版cmdb
安装
安装xadmin相对比较简单(python2.x)
pip install xadmin pip install django==1.9 pip install pymysql
python3.6
$ pip install xadmin 报错如下: long_description=open('README.rst').read(), UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4 in position 3444: illegal multibyte sequence ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in C:\Users\pinsily\AppData\Local\Temp\pip-build-xzvebsss\xadmin\ #问题解决 到 https://github.com/sshwsfc/xadmin 下载xadmin包 创建空白 README.rst 替换原来的 README.rst并压缩成zip包 pip install D:\\xadmin.zip 安装 $ pip install django==1.11.6 $ pip install pymysql
安装完成后,xadmin默认在/usr/lib/python2.x(3.x)/site-packeages/xadmin目录下
使用
首先创建一个django项目
django-admin startproject cmdb
配置django 加载xadmin app
$ cat cmdb/settings.py
import os from django.utils.translation import ugettext_lazy as _ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = 'z$%pa&ryr-f6$i6b9+t@29u+w^$mai1$3@24*sgv)5sc-on0@q' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'xadmin', #添加,注册xadmin app 'crispy_forms', ] MIDDLEWARE_CLASSES = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'cmdb.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'cmdb.wsgi.application' # Database # https://docs.djangoproject.com/en/1.11/ref/settings/#databases #DATABASES = { # 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), # } #}
#配置mysql数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'testcmdb', 'USER': 'cmdb', 'PASSWORD': '123456', 'HOST': '192.168.177.33', 'PORT': '3306' } } AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] #LANGUAGE_CODE = 'en-us'
#添加中文语言支持 LANGUAGE_CODE = 'zh-hans' LANGUAGES = [ ('en', _('English')), ('zh-hans', _('Simplified Chinese')), ('zh-hant', _('Traditional Chinese')), ] TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True STATIC_URL = '/static/'
url设置
$ cat cmdb/urls.py from django.conf.urls import url from django.contrib import admin import xadmin urlpatterns = [ #url(r'^admin/', admin.site.urls), url(r'^xadmin/', xadmin.site.urls), ]
引用mysql模块配置
$ cat cmdb/__init__.py import pymysql pymysql.install_as_MySQLdb()
配置数据库
#创建用户 $ grant all privileges on *.* to 'cmdb'@'%' identified by '123456' #创建数据库 create database testcmdb DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
初始化xadmin数据库
$ cd cmdb
$ python manage.py makemigrations xadmin
$ python manage.py migrate xadmin
#创建超级用户
python manage.py createsuperuser
#注意:python2.x 创建的时候,提示last_login不能为空,导致创建不成功:解决办法:
登录数据库执行
use cmdb
alter table auth_user modify column last_login datetime default null ;
创建完用用户后,启动django项目
python manage.py runserver 0.0.0.0:8000
登录成功后提示如下错误
AttributeError at /admin/ 'WSGIRequest' object has no attribute 'user' Request Method: GET Request URL: http://localhost:8000/admin/ Django Version: 1.9.2 Exception Type: AttributeError Exception Value: 'WSGIRequest' object has no attribute 'user' Exception Location: C:\Python27\lib\site-packages\django\contrib\admin\sites.py in has_permission, line 162 Python Executable: C:\Python27\python.exe
问题解决:
settings.py下 MIDDLEWARE配置的有问题,1.10之前,中间件的key为MIDDLEWARE_CLASSES, 1.10之后,为MIDDLEWARE。
MIDDLEWARE_CLASSES = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
打开页面登录如下所示

创建cmdb
创建数据库模型
[root@master xadmin]# cd /usr/lib/python2.7/site-packages/xadmin [root@master xadmin]# cat models.py #!/usr/bin/env python #-*-coding:UTF-8-*- import json import django from django.db import models from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ from django.core.urlresolvers import reverse from django.core.serializers.json import DjangoJSONEncoder from django.db.models.base import ModelBase from django.utils.encoding import smart_unicode from django.db.models.signals import post_migrate from django.contrib.auth.models import Permission import datetime import decimal if 4 < django.VERSION[1] < 7: AUTH_USER_MODEL = django.contrib.auth.get_user_model() else: AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') def add_view_permissions(sender, **kwargs): """ This syncdb hooks takes care of adding a view permission too all our content types. """ # for each of our content types for content_type in ContentType.objects.all(): # build our permission slug codename = "view_%s" % content_type.model # if it doesn't exist.. if not Permission.objects.filter(content_type=content_type, codename=codename): # add it Permission.objects.create(content_type=content_type, codename=codename, name="Can view %s" % content_type.name) #print "Added view permission for %s" % content_type.name # check for all our view permissions after a syncdb post_migrate.connect(add_view_permissions) class Bookmark(models.Model): title = models.CharField(_(u'Title'), max_length=128) user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user"), blank=True, null=True) url_name = models.CharField(_(u'Url Name'), max_length=64) content_type = models.ForeignKey(ContentType) query = models.CharField(_(u'Query String'), max_length=1000, blank=True) is_share = models.BooleanField(_(u'Is Shared'), default=False) @property def url(self): base_url = reverse(self.url_name) if self.query: base_url = base_url + '?' + self.query return base_url def __unicode__(self): return self.title class Meta: verbose_name = _(u'Bookmark') verbose_name_plural = _('Bookmarks') class JSONEncoder(DjangoJSONEncoder): def default(self, o): if isinstance(o, datetime.date): return o.strftime('%Y-%m-%d') elif isinstance(o, datetime.datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(o, decimal.Decimal): return str(o) elif isinstance(o, ModelBase): return '%s.%s' % (o._meta.app_label, o._meta.model_name) else: try: return super(JSONEncoder, self).default(o) except Exception: return smart_unicode(o) class UserSettings(models.Model): user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) key = models.CharField(_('Settings Key'), max_length=256) value = models.TextField(_('Settings Content')) def json_value(self): return json.loads(self.value) def set_json(self, obj): self.value = json.dumps(obj, cls=JSONEncoder, ensure_ascii=False) def __unicode__(self): return "%s %s" % (self.user, self.key) class Meta: verbose_name = _(u'User Setting') verbose_name_plural = _('User Settings') class UserWidget(models.Model): user = models.ForeignKey(AUTH_USER_MODEL, verbose_name=_(u"user")) page_id = models.CharField(_(u"Page"), max_length=256) widget_type = models.CharField(_(u"Widget Type"), max_length=50) value = models.TextField(_(u"Widget Params")) def get_value(self): value = json.loads(self.value) value['id'] = self.id value['type'] = self.widget_type return value def set_value(self, obj): self.value = json.dumps(obj, cls=JSONEncoder, ensure_ascii=False) def save(self, *args, **kwargs): created = self.pk is None super(UserWidget, self).save(*args, **kwargs) if created: try: portal_pos = UserSettings.objects.get( user=self.user, key="dashboard:%s:pos" % self.page_id) portal_pos.value = "%s,%s" % (self.pk, portal_pos.value) if portal_pos.value else self.pk portal_pos.save() except Exception: pass def __unicode__(self): return "%s %s widget" % (self.user, self.widget_type) class Meta: verbose_name = _(u'User Widget') verbose_name_plural = _('User Widgets') #自定义models #物理服务器 class Physic_Server(models.Model): asset_type_choices = ( ('server', u'服务器'), ('networkdevice', u'网络设备'), ('storagedevice', u'存储设备'), ('securitydevice', u'安全设备'), ('otherdevice', u'其它设备'), ) asset_type = models.CharField(choices=asset_type_choices,verbose_name="设备类型",max_length=64, default='server') asset_name = models.CharField(verbose_name=u"设备名称", max_length=32, blank=True, null=True) manufactory = models.ForeignKey('Manufactory', verbose_name=u'设备供应商', null=True, blank=True, on_delete=models.CASCADE) asset_get_type_choices = ( (0, "购买"), (1, "租赁") ) asset_get_type = models.SmallIntegerField(verbose_name="设备获取类型", choices=asset_get_type_choices, default=0) asset_model = models.CharField(verbose_name=u'设备型号', max_length=128, null=True, blank=True) asset_sn = models.CharField(u'设备编号', max_length=128, unique=True,null=True) asset_ip_address = models.CharField(u'IP地址',max_length=64,blank=True,null=True) asset_mac_address = models.CharField(u'MAC地址',max_length=64,unique=True,blank=True,null=True) asset_Cpu = models.CharField(u'CPU',max_length=24, blank=True,null=True) asset_raid_type = models.CharField(u'raid类型',max_length=512, blank=True,null=True) asset_Memory = models.CharField(u'内存大小',max_length=24,blank=True,null=True) asset_Disk = models.CharField(max_length=32, verbose_name="硬盘大小(GB)", blank=True,null=True) asset_Disk_memo = models.TextField(u'硬盘信息备注', null=True, blank=True) status_choices = ( (0,'在线'), (1,'已下线'), (2,'未知'), (3,'故障'), (4,'备用'), ) status = models.SmallIntegerField(verbose_name="状态",choices=status_choices,default=0) asset_admin = models.ForeignKey(AUTH_USER_MODEL, verbose_name=u'设备管理员',null=True) asset_price = models.CharField(u'设备价格', max_length=8,null=True, blank=True) asset_contract = models.ForeignKey('Contract', verbose_name=u'设备合同',null=True, blank=True,on_delete=models.CASCADE) asset_idc = models.ForeignKey('IDC', verbose_name=u'IDC机房', null=True, blank=True, on_delete=models.CASCADE) asset_trade_date = models.DateField(u'设备购买时间',null=True, blank=True) asset_expire_date = models.DateField(u'设备过保时间',null=True, blank=True) memo = models.TextField(u'备注', null=True, blank=True) class Meta: verbose_name = '设备' verbose_name_plural = "设备列表" def __str__(self): return self.asset_name #服务器类 #@python_2_unicode_compatible class Server(models.Model): hosted_on_server = models.ForeignKey('Physic_Server',null=True, verbose_name="所属服务器",blank=True,on_delete=models.CASCADE) server_name = models.CharField(verbose_name=u"虚拟机名称", max_length=32, blank=True, null=True) business_unit = models.ForeignKey('BusinessUnit', verbose_name=u'所属业务线', null=True, blank=True,on_delete=models.CASCADE) ip_address = models.CharField(u'IP地址',max_length=64,blank=True,null=True) mac_address = models.CharField(u'MAC地址',max_length=64,blank=True,null=True) Cpu = models.CharField(u'CPU',max_length=24, blank=True,null=True) raid_type = models.CharField(u'raid类型',max_length=512, blank=True,null=True) Memory = models.CharField(u'内存大小',max_length=24,blank=True,null=True) Disk = models.CharField(max_length=32, verbose_name="硬盘大小(GB)", blank=True,null=True) Disk_memo = models.TextField(u'硬盘信息备注', null=True, blank=True) os_type = models.CharField(u'操作系统类型',max_length=64, blank=True,null=True) os_distribution =models.CharField(u'发型版本',max_length=64, blank=True,null=True) os_release = models.CharField(u'操作系统版本',max_length=64, blank=True,null=True) memo = models.TextField(u'备注', null=True, blank=True) class Meta: verbose_name = '虚拟机' verbose_name_plural = "虚拟机" # unique_together = ("hosted_on_server", "server_name") def __str__(self): return self.server_name #个人电脑 #@python_2_unicode_compatible class Personal_device(models.Model): sub_assset_type_choices =( (0,'台式机'), (1,'笔记本') ) system_os_choices = ( (0,'Windows'), (1,'MAC OS'), ) sub_asset_type = models.SmallIntegerField(choices=sub_assset_type_choices,verbose_name="电脑类型",default=0) asset_get_type_choices = ( (0, "购买"), (1, "租赁") ) asset_get_type = models.SmallIntegerField(verbose_name="获取类型", choices=asset_get_type_choices, default=0) status_choices = ( (0, '使用中'), (1, '未使用'), (2, '未知'), (3, '故障'), ) status = models.SmallIntegerField(verbose_name="状态", choices=status_choices, default=0) user = models.CharField(verbose_name=u"使用人",max_length=32,blank=True,null=True) ip_address = models.CharField(u'IP地址',max_length=64,blank=True,null=True) mac_address = models.CharField(u'MAC地址',max_length=64,blank=True,null=True) model = models.CharField(verbose_name=u'型号', max_length=128, null=True, blank=True) cpu = models.CharField(u'CPU',max_length=24, blank=True,null=True) Memory = models.CharField(u'内存',blank=True,max_length=12,null=True) Disk = models.CharField(max_length=32, verbose_name="硬盘大小(GB)", blank=True,null=True) Disk_memo = models.TextField(u'硬盘信息备注', null=True, blank=True) os_type = models.SmallIntegerField(choices=system_os_choices,verbose_name=u'操作系统',default=0) os_release = models.CharField(u'操作系统版本',max_length=64, blank=True,null=True) create_date = models.DateTimeField(verbose_name="购买时间",blank=True, auto_now_add=True) price = models.CharField(u'价格', max_length=32, null=True, blank=True) memo = models.TextField(u'备注', null=True, blank=True) class Meta: verbose_name = '办公电脑' verbose_name_plural = "办公电脑" class Manufactory(models.Model): manufactory = models.CharField(u'供应商名称',max_length=64, unique=True) support_num = models.CharField(u'支持电话',max_length=30,blank=True) memo = models.CharField(u'备注',max_length=128,blank=True) def __str__(self): return self.manufactory class Meta: verbose_name = '供应商' verbose_name_plural = "供应商" class BusinessUnit(models.Model): parent_unit = models.ForeignKey('self',related_name='parent_level',blank=True,null=True,on_delete=models.CASCADE) name = models.CharField(u'业务线',max_length=64) memo = models.CharField(u'备注',max_length=64, blank=True) def __str__(self): return self.name class Meta: verbose_name = '业务线' verbose_name_plural = "业务线" unique_together = ("parent_unit","name") #@python_2_unicode_compatible class Contract(models.Model): sn = models.CharField(u'合同号', max_length=128,unique=True) name = models.CharField(u'合同名称', max_length=64 ) memo = models.TextField(u'备注', blank=True,null=True) price = models.IntegerField(u'合同金额') detail = models.TextField(u'合同详细',blank=True,null=True) start_date = models.DateField(blank=True,) end_date = models.DateField(blank=True) license_num = models.IntegerField(u'license数量',blank=True,null=True) create_date = models.DateField(auto_now_add=True) update_date= models.DateField(auto_now=True) class Meta: verbose_name = '合同' verbose_name_plural = "合同" def __str__(self): return self.name #@python_2_unicode_compatible class IDC(models.Model): name = models.CharField(u'机房名称',max_length=64,unique=True) memo = models.CharField(u'备注',max_length=128,blank=True,null=True) def __str__(self): return self.name class Meta: verbose_name = '机房' verbose_name_plural = "机房" class EventLog(models.Model): name = models.CharField(u'事件名称', max_length=100) event_type_choices = ( (1,u'硬件变更'), (2,u'新增配件'), (3,u'设备下线'), (4,u'设备上线'), (5,u'定期维护'), (6,u'业务上线\更新\变更'), (7,u'其它'), ) event_type = models.SmallIntegerField(u'事件类型', choices= event_type_choices) asset = models.ForeignKey('Physic_Server',null=True,on_delete=models.CASCADE) component = models.CharField('事件子项',max_length=255, blank=True,null=True) detail = models.TextField(u'事件详情') date = models.DateTimeField(u'事件时间',auto_now_add=True) admin = models.ForeignKey(AUTH_USER_MODEL, verbose_name=u'事件源') memo = models.TextField(u'备注', blank=True,null=True) def __str__(self): return self.name class Meta: verbose_name = '事件纪录' verbose_name_plural = "事件纪录" def colored_event_type(self): if self.event_type == 1: cell_html = '<span style="background: orange;">%s</span>' elif self.event_type == 2 : cell_html = '<span style="background: yellowgreen;">%s</span>' else: cell_html = '<span >%s</span>' return cell_html % self.get_event_type_display() colored_event_type.allow_tags = True colored_event_type.short_description = u'事件类型'
admin注册models
[root@master xadmin]# cat adminx.py #/usr/bin/env python #-*-coding:utf-8-*- from __future__ import absolute_import import xadmin from xadmin.models import * from xadmin.layout import * from django import forms from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.utils.translation import ugettext_lazy as _, ugettext from xadmin import views class UserSettingsAdmin(object): model_icon = 'fa fa-cog' hidden_menu = True xadmin.site.register(UserSettings, UserSettingsAdmin) class PhysicServerAdmin(object): list_display = ('id','asset_name','asset_type','asset_model','asset_sn','asset_ip_address','asset_mac_address', 'asset_Cpu', 'asset_Memory', 'asset_Disk','status') search_fields = ['asset_ip_address','status'] # 搜索时可输入的字段内容 #list_display_links = ('id',) # 点击id可进入详细界面进行编辑 list_filter = ['status'] list_per_page = 20 # 每页显示多少条 ordering = ('id',) show_detail_fields = ['asset_name'] #显示本条数据的所有信息 xadmin.site.register(Physic_Server, PhysicServerAdmin) class ServerAdmin(object): list_display = ('id','server_name','hosted_on_server','ip_address','mac_address', 'Cpu', 'Memory', 'Disk', 'business_unit',) search_fields = ['ip_address', 'business_unit'] # 搜索时可输入的字段内容 #list_display_links = ('id',) # 点击id可进入详细界面进行编辑 list_per_page = 20 # 每页显示多少条 ordering = ('id',) show_detail_fields = ['server_name'] #显示本条数据的所有信息 xadmin.site.register(Server, ServerAdmin) class PersonalAdmin(object): list_display = ('user','sub_asset_type','model','ip_address','mac_address', 'cpu', 'Memory', 'Disk', 'os_type','status','create_date') search_fields = ['user', 'mac_address','ip_address','status'] # 搜索时可输入的字段内容 #list_display_links = ('id',) # 点击id可进入详细界面进行编辑 list_per_page = 20 # 每页显示多少条 list_filter = ['status'] #过滤器 ordering = ('id',) xadmin.site.register(Personal_device, PersonalAdmin) xadmin.site.register(BusinessUnit) xadmin.site.register(IDC) xadmin.site.register(Contract) xadmin.site.register(Manufactory) # xadmin.site.register(Tag) # xadmin.site.register(Software) class EventLogAdmin(object): list_display = ('name','colored_event_type','asset','component','detail','date','user') #详细信息界面显示的字段 search_fields = ('asset',) list_filter = ('event_type','component','date','admin') xadmin.site.register(EventLog, EventLogAdmin)
重启cmdb项目打开页面如下所示

自定义网站title
admin.py from xadmin import views class GlobalSetting(object): site_title = "xx资产管理系统" site_footer = "xxx" # xadmin.site.register(views.CommAdminView, GlobalSetting)
效果如图

设置App名称,如下图所示

#apps.py class XAdminConfig(AppConfig): """Simple AppConfig which does not do automatic discovery.""" name = 'xadmin' verbose_name = _("CMDB资产管理") #__init__.py ,默认已经设置,不需要改动 default_app_config = 'xadmin.apps.XAdminConfig'
显示数据详情,如下:

点击红色方框中的"i",可查看整条数据的详情,或查看相关外键的详细信息
class PhysicServerAdmin(object): list_display = ('id','asset_name','asset_type','asset_model','asset_sn','asset_ip_address','asset_mac_address', 'asset_Cpu', 'asset_Memory', 'asset_Disk','status') search_fields = ['asset_ip_address','status'] # 搜索时可输入的字段内容 #list_display_links = ('id',) # 点击id可进入详细界面进行编辑(默认的) list_filter = ['status'] list_per_page = 20 # 每页显示多少条 ordering = ('id',) #根据id排序
readonly_fields = ('user_email',) #设置只读字段
show_detail_fields = ['asset_name'] #显示本条数据的所有信息

浙公网安备 33010602011771号