diff --git a/api/views.py b/api/views.py index 68517d14..6691fd73 100644 --- a/api/views.py +++ b/api/views.py @@ -1,6 +1,7 @@ import copy from rest_framework import viewsets, permissions from api.serializers import * +from assets.models import BusinessLine class CustomDjangoModelPermission(permissions.DjangoModelPermissions): @@ -20,10 +21,40 @@ class InventoryViewSet(viewsets.ModelViewSet): class AssetsViewSet(viewsets.ModelViewSet): - queryset = Assets.objects.all().order_by('id') + queryset = Assets.objects.select_related( + 'asset_business_line', + 'asset_business_owner', + 'asset_admin', + 'asset_provider', + 'asset_idc', + 'asset_cabinet' + ).order_by('id') serializer_class = AssetsSerializer permission_classes = (CustomDjangoModelPermission,) + def get_queryset(self): + queryset = super().get_queryset() + business_line_id = self.request.query_params.get('business_line') + business_owner_id = self.request.query_params.get('business_owner') + + if business_line_id: + try: + bl_id = int(business_line_id) + children_ids = BusinessLine.get_all_children_ids(bl_id) + all_ids = [bl_id] + children_ids + queryset = queryset.filter(asset_business_line_id__in=all_ids) + except (ValueError, TypeError): + pass + + if business_owner_id: + try: + owner_id = int(business_owner_id) + queryset = queryset.filter(asset_business_owner_id=owner_id) + except (ValueError, TypeError): + pass + + return queryset + class ServerAssetsViewSet(viewsets.ModelViewSet): queryset = ServerAssets.objects.all().order_by('id') diff --git a/assets/admin.py b/assets/admin.py index ea5d68b7..7d9b667e 100644 --- a/assets/admin.py +++ b/assets/admin.py @@ -1,3 +1,193 @@ from django.contrib import admin +from django import forms +from assets.models import Assets, ServerAssets, NetworkAssets, OfficeAssets, SecurityAssets, StorageAssets, SoftwareAssets, DiskAssets, RamAssets, NetworkCardAssets, AssetProvider, IDC, Cabinet, AdminRecord, ZabbixAlert, WebSite, BusinessLine, PullAssetConf -# Register your models here. + +class BusinessLineForm(forms.ModelForm): + class Meta: + model = BusinessLine + fields = '__all__' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.instance and self.instance.pk: + exclude_ids = [self.instance.pk] + [child.pk for child in self.instance.get_all_children()] + self.fields['parent'].queryset = BusinessLine.objects.exclude(pk__in=exclude_ids) + + +class BusinessLineAdmin(admin.ModelAdmin): + form = BusinessLineForm + list_display = ('name', 'parent', 'level', 'sort', 'get_full_path', 'memo') + list_filter = ('level', 'parent') + search_fields = ('name', 'memo') + ordering = ('sort', 'id') + list_editable = ('sort',) + + def get_full_path(self, obj): + return obj.get_full_path() + get_full_path.short_description = '完整路径' + + def save_model(self, request, obj, form, change): + if obj.parent: + obj.level = obj.parent.level + 1 + else: + obj.level = 1 + super().save_model(request, obj, form, change) + + +class AssetsAdmin(admin.ModelAdmin): + list_display = ('asset_type', 'asset_nu', 'asset_model', 'asset_status', 'asset_management_ip', + 'asset_admin', 'business_line_display', 'business_owner_display', 'asset_idc', 'asset_cabinet') + list_filter = ('asset_type', 'asset_status', 'asset_admin', 'asset_idc', 'asset_business_line', 'asset_business_owner') + search_fields = ('asset_nu', 'asset_model', 'asset_management_ip') + readonly_fields = ('asset_create_time', 'asset_update_time') + fieldsets = ( + ('基本信息', { + 'fields': ('asset_type', 'asset_nu', 'asset_model', 'asset_status', 'asset_memo') + }), + ('业务信息', { + 'fields': ('asset_business_line', 'asset_business_owner') + }), + ('管理信息', { + 'fields': ('asset_admin', 'asset_management_ip') + }), + ('位置信息', { + 'fields': ('asset_provider', 'asset_idc', 'asset_cabinet') + }), + ('财务信息', { + 'fields': ('asset_purchase_day', 'asset_expire_day', 'asset_price') + }), + ('时间信息', { + 'fields': ('asset_create_time', 'asset_update_time'), + 'classes': ('collapse',) + }), + ) + + def business_line_display(self, obj): + if obj.asset_business_line: + return obj.asset_business_line.get_full_path() + return '-' + business_line_display.short_description = '所属业务线' + + def business_owner_display(self, obj): + if obj.asset_business_owner: + return obj.asset_business_owner.username + return '-' + business_owner_display.short_description = '业务负责人' + + +class ServerAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'server_type', 'hostname', 'system', 'system_version', 'port') + list_filter = ('server_type', 'system') + search_fields = ('hostname', 'assets__asset_nu', 'assets__asset_management_ip') + + +class NetworkAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'network_type') + list_filter = ('network_type',) + search_fields = ('assets__asset_nu', 'assets__asset_management_ip') + + +class OfficeAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'office_type') + list_filter = ('office_type',) + search_fields = ('assets__asset_nu', 'assets__asset_management_ip') + + +class SecurityAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'security_type') + list_filter = ('security_type',) + search_fields = ('assets__asset_nu', 'assets__asset_management_ip') + + +class StorageAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'storage_type') + list_filter = ('storage_type',) + search_fields = ('assets__asset_nu', 'assets__asset_management_ip') + + +class SoftwareAssetsAdmin(admin.ModelAdmin): + list_display = ('assets', 'software_type') + list_filter = ('software_type',) + search_fields = ('assets__asset_nu',) + + +class DiskAssetsAdmin(admin.ModelAdmin): + list_display = ('asset', 'disk_slot', 'disk_volume', 'disk_model', 'disk_brand', 'disk_status') + list_filter = ('disk_status', 'disk_brand') + search_fields = ('asset__asset_nu', 'disk_serial', 'disk_model') + + +class RamAssetsAdmin(admin.ModelAdmin): + list_display = ('asset', 'ram_slot', 'ram_volume', 'ram_brand', 'ram_serial') + list_filter = ('ram_brand',) + search_fields = ('asset__asset_nu', 'ram_serial') + + +class NetworkCardAssetsAdmin(admin.ModelAdmin): + list_display = ('asset', 'network_card_name', 'network_card_mac', 'network_card_ip', 'network_card_status') + list_filter = ('network_card_status',) + search_fields = ('asset__asset_nu', 'network_card_mac', 'network_card_ip') + + +class AssetProviderAdmin(admin.ModelAdmin): + list_display = ('asset_provider_name', 'asset_provider_contact', 'asset_provider_telephone', 'asset_provider_memo') + search_fields = ('asset_provider_name', 'asset_provider_contact') + + +class IDCAdmin(admin.ModelAdmin): + list_display = ('idc_name', 'idc_address', 'idc_contact', 'idc_telephone', 'idc_memo') + search_fields = ('idc_name', 'idc_address') + + +class CabinetAdmin(admin.ModelAdmin): + list_display = ('idc', 'cabinet_name', 'cabinet_memo') + list_filter = ('idc',) + search_fields = ('cabinet_name',) + + +class AdminRecordAdmin(admin.ModelAdmin): + list_display = ('admin_login_user', 'admin_server', 'admin_remote_ip', 'admin_start_time', + 'admin_login_status_time', 'admin_record_mode') + list_filter = ('admin_record_mode', 'admin_login_user') + search_fields = ('admin_server', 'admin_remote_ip') + readonly_fields = ('admin_login_user', 'admin_server', 'admin_remote_ip', 'admin_start_time', + 'admin_login_status_time', 'admin_record_mode', 'admin_record_file', 'admin_record_cmds') + + +class ZabbixAlertAdmin(admin.ModelAdmin): + list_display = ('alert_date', 'alert_num') + list_filter = ('alert_date',) + date_hierarchy = 'alert_date' + + +class WebSiteAdmin(admin.ModelAdmin): + list_display = ('web_name', 'web_address', 'web_des') + search_fields = ('web_name', 'web_address') + + +class PullAssetConfAdmin(admin.ModelAdmin): + list_display = ('conf_name', 'cloud_name', 'cloud_region', 'belong_user', 'server_user', 'server_port') + list_filter = ('cloud_name',) + search_fields = ('conf_name', 'cloud_region') + readonly_fields = ('access_key',) + + +admin.site.register(BusinessLine, BusinessLineAdmin) +admin.site.register(Assets, AssetsAdmin) +admin.site.register(ServerAssets, ServerAssetsAdmin) +admin.site.register(NetworkAssets, NetworkAssetsAdmin) +admin.site.register(OfficeAssets, OfficeAssetsAdmin) +admin.site.register(SecurityAssets, SecurityAssetsAdmin) +admin.site.register(StorageAssets, StorageAssetsAdmin) +admin.site.register(SoftwareAssets, SoftwareAssetsAdmin) +admin.site.register(DiskAssets, DiskAssetsAdmin) +admin.site.register(RamAssets, RamAssetsAdmin) +admin.site.register(NetworkCardAssets, NetworkCardAssetsAdmin) +admin.site.register(AssetProvider, AssetProviderAdmin) +admin.site.register(IDC, IDCAdmin) +admin.site.register(Cabinet, CabinetAdmin) +admin.site.register(AdminRecord, AdminRecordAdmin) +admin.site.register(ZabbixAlert, ZabbixAlertAdmin) +admin.site.register(WebSite, WebSiteAdmin) +admin.site.register(PullAssetConf, PullAssetConfAdmin) diff --git a/assets/models.py b/assets/models.py index 3427e1eb..ec5c3a3d 100644 --- a/assets/models.py +++ b/assets/models.py @@ -30,6 +30,10 @@ class Assets(models.Model): on_delete=models.PROTECT) asset_cabinet = models.ForeignKey('Cabinet', related_name='assets', null=True, blank=True, verbose_name='所在机柜', on_delete=models.PROTECT) + asset_business_line = models.ForeignKey('BusinessLine', related_name='assets', null=True, blank=True, + verbose_name='所属业务线', on_delete=models.SET_NULL) + asset_business_owner = models.ForeignKey('users.UserProfile', related_name='business_owner_assets', null=True, blank=True, + verbose_name='业务负责人', on_delete=models.SET_NULL) asset_purchase_day = models.DateField(null=True, blank=True, verbose_name="购买日期") asset_expire_day = models.DateField(null=True, blank=True, verbose_name="过保日期") @@ -295,6 +299,73 @@ class Meta: verbose_name_plural = '常用网站表' +class BusinessLine(models.Model): + """业务线(支持多级分类)""" + name = models.CharField(max_length=100, verbose_name='业务线名称') + parent = models.ForeignKey('self', related_name='children', null=True, blank=True, + verbose_name='父级业务线', on_delete=models.CASCADE) + level = models.SmallIntegerField(default=1, verbose_name='层级') + sort = models.SmallIntegerField(default=0, verbose_name='排序') + memo = models.TextField(null=True, blank=True, verbose_name='备注') + + class Meta: + db_table = 'ops_business_line' + verbose_name = '业务线表' + verbose_name_plural = '业务线表' + ordering = ['sort', 'id'] + + def __str__(self): + return self.get_full_path() + + def get_full_path(self, all_bl_map=None): + """获取完整层级路径""" + if all_bl_map is None: + path = [self.name] + parent = self.parent + while parent: + path.insert(0, parent.name) + parent = parent.parent + return '/'.join(path) + else: + path = [self.name] + current = self + while current.parent_id and current.parent_id in all_bl_map: + current = all_bl_map[current.parent_id] + path.insert(0, current.name) + return '/'.join(path) + + @classmethod + def get_all_children_ids(cls, parent_id): + """获取所有子业务线的ID(包括嵌套的子业务线)""" + all_bl = list(cls.objects.values('id', 'parent_id')) + bl_map = {} + for bl in all_bl: + pid = bl['parent_id'] + if pid not in bl_map: + bl_map[pid] = [] + bl_map[pid].append(bl['id']) + + result = [] + stack = [parent_id] + while stack: + pid = stack.pop() + if pid in bl_map: + children = bl_map[pid] + result.extend(children) + stack.extend(children) + return result + + @classmethod + def get_full_path_map(cls): + """获取所有业务线的完整路径映射""" + all_bl = list(cls.objects.all()) + bl_map = {bl.id: bl for bl in all_bl} + path_map = {} + for bl in all_bl: + path_map[bl.id] = bl.get_full_path(bl_map) + return path_map + + class PullAssetConf(models.Model): cloud_names = ( ('ali', '阿里云'), diff --git a/assets/templatetags/custom_tags.py b/assets/templatetags/custom_tags.py index 4f719a77..19bd803b 100644 --- a/assets/templatetags/custom_tags.py +++ b/assets/templatetags/custom_tags.py @@ -43,3 +43,10 @@ def union_user_plan(self_plan, attention_plan): @register.filter def user_plan_count(plan_list): return len(plan_list) + + +@register.filter +def get_item(dictionary, key): + if dictionary and isinstance(dictionary, dict): + return dictionary.get(key, '') + return '' diff --git a/assets/views.py b/assets/views.py index 2962674c..314b04af 100644 --- a/assets/views.py +++ b/assets/views.py @@ -77,13 +77,58 @@ def get_assets_charts(request): def get_assets_list(request): asset_types = Assets.asset_types asset_status = request.GET.get('asset_status') - assets = None + business_line_id = request.GET.get('business_line') + business_owner_id = request.GET.get('business_owner') + + path_map = BusinessLine.get_full_path_map() + + assets = Assets.objects.select_related( + 'serverassets', + 'asset_business_line', + 'asset_business_owner', + 'asset_admin', + 'asset_idc', + 'asset_cabinet' + ).prefetch_related( + 'network_card_assets', + 'service_set__project' + ) + if asset_status: db_status = tuple(filter(lambda x: x[1] == asset_status, Assets.asset_status_))[0][0] - assets = Assets.objects.filter(asset_status=db_status) - else: - assets = Assets.objects.select_related('serverassets') - return render(request, 'assets/assets_list.html', locals()) + assets = assets.filter(asset_status=db_status) + + if business_line_id: + try: + bl_id = int(business_line_id) + children_ids = BusinessLine.get_all_children_ids(bl_id) + all_ids = [bl_id] + children_ids + assets = assets.filter(asset_business_line_id__in=all_ids) + except (ValueError, TypeError): + pass + + if business_owner_id: + try: + owner_id = int(business_owner_id) + assets = assets.filter(asset_business_owner_id=owner_id) + except (ValueError, TypeError): + pass + + all_business_lines = BusinessLine.objects.all() + business_owners = UserProfile.objects.all() + + for asset in assets: + if asset.asset_business_line_id: + asset._business_line_path = path_map.get(asset.asset_business_line_id, '') + + return render(request, 'assets/assets_list.html', { + 'assets': assets, + 'asset_types': asset_types, + 'all_business_lines': all_business_lines, + 'business_owners': business_owners, + 'path_map': path_map, + 'request': request, + }) @permission_required('assets.add_assets', raise_exception=True) @@ -101,6 +146,8 @@ def add_asset(request): asset_idcs = IDC.objects.all() asset_cabinets = Cabinet.objects.select_related('idc') server_assets = ServerAssets.objects.select_related('assets') + business_lines = BusinessLine.objects.all() + business_owners = UserProfile.objects.all() return render(request, 'assets/add_asset.html', locals()) @@ -196,8 +243,15 @@ def server_facts(request): @permission_required('assets.add_assets', raise_exception=True) def get_asset_info(request, pk): - asset = Assets.objects.get(id=pk) - return render(request, 'assets/asset_info.html', locals()) + asset = Assets.objects.select_related( + 'asset_business_line', + 'asset_business_owner', + 'asset_admin', + 'asset_provider', + 'asset_idc', + 'asset_cabinet' + ).get(id=pk) + return render(request, 'assets/asset_info.html', {'asset': asset}) @permission_required('assets.add_assets', raise_exception=True) diff --git a/templates/assets/add_asset.html b/templates/assets/add_asset.html index 1db0e1f9..6b7b507e 100644 --- a/templates/assets/add_asset.html +++ b/templates/assets/add_asset.html @@ -80,6 +80,28 @@ +
+ +
+ +
+
+
+ +
+ +
+
diff --git a/templates/assets/asset_info.html b/templates/assets/asset_info.html index 31a3bf4f..6ed74b2d 100644 --- a/templates/assets/asset_info.html +++ b/templates/assets/asset_info.html @@ -15,6 +15,8 @@

资产信息

状态 管理IP 资产管理员 + 所属业务线 + 业务负责人 所在机房 所在机柜 购买日期 @@ -42,6 +44,20 @@

资产信息

{{ asset.get_asset_status_display }} {{ asset.asset_management_ip }} {{ asset.asset_admin.username }} + + {% if asset.asset_business_line_id %} + {{ asset.asset_business_line.get_full_path }} + {% else %} + 未分配 + {% endif %} + + + {% if asset.asset_business_owner %} + {{ asset.asset_business_owner.username }} + {% else %} + + {% endif %} + {{ asset.asset_idc.idc_name }} {{ asset.asset_cabinet.cabinet_name }} {{ asset.asset_purchase_day|date:"Y-m-d" }} diff --git a/templates/assets/assets_list.html b/templates/assets/assets_list.html index 1769e570..d9757c49 100644 --- a/templates/assets/assets_list.html +++ b/templates/assets/assets_list.html @@ -57,6 +57,39 @@
+
+
+
+ + +
+
+ + +
+ + +
+
+