diff --git a/apps/assets/api.py b/apps/assets/api.py index 25d89c759..317ed51c2 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -17,25 +17,26 @@ from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView +from rest_framework.pagination import LimitOffsetPagination from django.shortcuts import get_object_or_404 from django.db.models import Q, Count -from rest_framework.pagination import LimitOffsetPagination from common.mixins import CustomFilterMixin from common.utils import get_logger from .hands import IsSuperUser, IsValidUser, IsSuperUserOrAppUser, \ get_user_granted_assets -from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser +from .models import AssetGroup, Asset, Cluster, SystemUser, AdminUser, Label from . import serializers from .tasks import update_asset_hardware_info_manual, test_admin_user_connectability_manual, \ test_asset_connectability_manual, push_system_user_to_cluster_assets_manual, \ test_system_user_connectability_manual +from .utils import LabelFilter logger = get_logger(__file__) -class AssetViewSet(CustomFilterMixin, BulkModelViewSet): +class AssetViewSet(CustomFilterMixin, LabelFilter, BulkModelViewSet): """ API endpoint that allows Asset to be viewed or edited. """ @@ -295,3 +296,15 @@ class SystemUserTestConnectiveApi(generics.RetrieveAPIView): system_user = self.get_object() test_system_user_connectability_manual.delay(system_user) return Response({"msg": "Task created"}) + + +class LabelViewSet(BulkModelViewSet): + queryset = Label.objects.annotate(asset_count=Count("assets")) + permission_classes = (IsSuperUser,) + serializer_class = serializers.LabelSerializer + + def list(self, request, *args, **kwargs): + if request.query_params.get("distinct"): + self.serializer_class = serializers.LabelDistinctSerializer + self.queryset = self.queryset.values("name").distinct() + return super().list(request, *args, **kwargs) diff --git a/apps/assets/forms.py b/apps/assets/forms.py index 824ef3fcd..38e1e65ef 100644 --- a/apps/assets/forms.py +++ b/apps/assets/forms.py @@ -2,28 +2,35 @@ from django import forms from django.utils.translation import gettext_lazy as _ -from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser +from .models import Cluster, Asset, AssetGroup, AdminUser, SystemUser, Label from common.utils import validate_ssh_private_key, ssh_pubkey_gen, ssh_key_gen, get_logger - logger = get_logger(__file__) class AssetCreateForm(forms.ModelForm): - class Meta: model = Asset fields = [ 'hostname', 'ip', 'public_ip', 'port', 'type', 'comment', 'cluster', 'groups', 'status', 'env', 'is_active', - 'admin_user' + 'admin_user', 'labels' ] widgets = { - 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'cluster': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select cluster')}), - 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _('Select admin user')}), - 'port': forms.TextInput() + 'groups': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select asset groups') + }), + 'cluster': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select cluster') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select labels') + }), + 'port': forms.TextInput(), } help_texts = { 'hostname': '* required', @@ -40,6 +47,14 @@ class AssetCreateForm(forms.ModelForm): raise forms.ValidationError(_("You need set a admin user if cluster not have")) return self.cleaned_data['admin_user'] + def is_valid(self): + print(self.data) + result = super().is_valid() + if not result: + print(self.errors) + print(self.cleaned_data) + return result + class AssetUpdateForm(forms.ModelForm): class Meta: @@ -47,11 +62,22 @@ class AssetUpdateForm(forms.ModelForm): fields = [ 'hostname', 'ip', 'port', 'groups', "cluster", 'is_active', 'type', 'env', 'status', 'public_ip', 'remote_card_ip', 'cabinet_no', - 'cabinet_pos', 'number', 'comment', 'admin_user', + 'cabinet_pos', 'number', 'comment', 'admin_user', 'labels' ] widgets = { - 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), - 'admin_user': forms.Select(attrs={'class': 'select2', 'data-placeholder': _("Default using cluster admin user")}) + 'groups': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select asset groups') + }), + 'cluster': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select cluster') + }), + 'admin_user': forms.Select(attrs={ + 'class': 'select2', 'data-placeholder': _('Select admin user') + }), + 'labels': forms.SelectMultiple(attrs={ + 'class': 'select2', 'data-placeholder': _('Select labels') + }), + 'port': forms.TextInput(), } help_texts = { 'hostname': '* required', @@ -68,13 +94,15 @@ class AssetUpdateForm(forms.ModelForm): raise forms.ValidationError(_("You need set a admin user if cluster not have")) return self.cleaned_data['admin_user'] + def is_valid(self): + print(self.data) + return super().is_valid() + class AssetBulkUpdateForm(forms.ModelForm): assets = forms.ModelMultipleChoiceField( - required=True, - help_text='* required', - label=_('Select assets'), - queryset=Asset.objects.all(), + required=True, help_text='* required', + label=_('Select assets'), queryset=Asset.objects.all(), widget=forms.SelectMultiple( attrs={ 'class': 'select2', @@ -83,10 +111,7 @@ class AssetBulkUpdateForm(forms.ModelForm): ) ) port = forms.IntegerField( - label=_('Port'), - required=False, - min_value=1, - max_value=65535, + label=_('Port'), required=False, min_value=1, max_value=65535, ) class Meta: @@ -96,7 +121,9 @@ class AssetBulkUpdateForm(forms.ModelForm): 'type', 'env', ] widgets = { - 'groups': forms.SelectMultiple(attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')}), + 'groups': forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Select asset groups')} + ), } def save(self, commit=True): @@ -140,7 +167,7 @@ class AssetGroupForm(forms.ModelForm): def save(self, commit=True): group = super().save(commit=commit) - assets= self.cleaned_data['assets'] + assets = self.cleaned_data['assets'] group.assets.set(assets) return group @@ -377,3 +404,28 @@ class SystemUserAuthForm(forms.Form): class FileForm(forms.Form): file = forms.FileField() + + +class LabelForm(forms.ModelForm): + assets = forms.ModelMultipleChoiceField( + queryset=Asset.objects.all(), label=_('Asset'), required=False, + widget=forms.SelectMultiple( + attrs={'class': 'select2', 'data-placeholder': _('Select assets')} + ) + ) + + class Meta: + model = Label + fields = ['name', 'value', 'assets'] + + def __init__(self, *args, **kwargs): + if kwargs.get('instance', None): + initial = kwargs.get('initial', {}) + initial['assets'] = kwargs['instance'].assets.all() + super().__init__(*args, **kwargs) + + def save(self, commit=True): + label = super().save(commit=commit) + assets = self.cleaned_data['assets'] + label.assets.set(assets) + return label diff --git a/apps/assets/models/__init__.py b/apps/assets/models/__init__.py index 38ed90721..6e0621ba8 100644 --- a/apps/assets/models/__init__.py +++ b/apps/assets/models/__init__.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- # from .user import AdminUser, SystemUser +from .label import Label from .cluster import * from .group import * from .asset import * diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index c83443077..0b1cdf0ec 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -88,6 +88,8 @@ class Asset(models.Model): os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch')) hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw')) + labels = models.ManyToManyField('assets.Label', blank=True, related_name='assets', verbose_name=_("Labels")) + created_by = models.CharField(max_length=32, null=True, blank=True, verbose_name=_('Created by')) date_created = models.DateTimeField(auto_now_add=True, null=True, blank=True, verbose_name=_('Date created')) comment = models.TextField(max_length=128, default='', blank=True, verbose_name=_('Comment')) diff --git a/apps/assets/models/label.py b/apps/assets/models/label.py new file mode 100644 index 000000000..990a71ca8 --- /dev/null +++ b/apps/assets/models/label.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# + +import uuid +from django.db import models +from django.utils.translation import ugettext_lazy as _ + + +class Label(models.Model): + SYSTEM_CATEGORY = "S" + USER_CATEGORY = "U" + CATEGORY_CHOICES = ( + ("S", _("System")), + ("U", _("User")) + ) + id = models.UUIDField(default=uuid.uuid4, primary_key=True) + name = models.CharField(max_length=128, verbose_name=_("Name")) + value = models.CharField(max_length=128, verbose_name=_("Value")) + category = models.CharField(max_length=128, choices=CATEGORY_CHOICES, default=USER_CATEGORY, verbose_name=_("Category")) + is_active = models.BooleanField(default=True, verbose_name=_("Is active")) + comment = models.TextField(blank=True, null=True, verbose_name=_("Comment")) + date_created = models.DateTimeField( + auto_now_add=True, null=True, blank=True, verbose_name=_('Date created') + ) + + @classmethod + def get_queryset_group_by_name(cls): + names = cls.objects.values_list('name', flat=True) + for name in names: + yield name, cls.objects.filter(name=name) + + def __str__(self): + return "{}:{}".format(self.name, self.value) + + class Meta: + db_table = "assets_label" + unique_together = ('name', 'value') diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index bef50c5f0..1fd6e1d9a 100644 --- a/apps/assets/serializers.py +++ b/apps/assets/serializers.py @@ -4,7 +4,7 @@ from rest_framework import serializers from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin -from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser +from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser, Label from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY @@ -286,3 +286,34 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): return len(obj.assets_granted) + + +class LabelSerializer(serializers.ModelSerializer): + asset_count = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = '__all__' + list_serializer_class = BulkListSerializer + + @staticmethod + def get_asset_count(obj): + return obj.asset_count + + def get_field_names(self, declared_fields, info): + fields = super().get_field_names(declared_fields, info) + fields.extend(['get_category_display']) + return fields + + +class LabelDistinctSerializer(serializers.ModelSerializer): + value = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = ("name", "value") + + @staticmethod + def get_value(obj): + labels = Label.objects.filter(name=obj["name"]) + return ', '.join([label.value for label in labels]) diff --git a/apps/assets/templates/assets/asset_create.html b/apps/assets/templates/assets/asset_create.html index f96f1b0fb..0fb2f6002 100644 --- a/apps/assets/templates/assets/asset_create.html +++ b/apps/assets/templates/assets/asset_create.html @@ -2,6 +2,8 @@ {% load static %} {% load bootstrap3 %} {% load i18n %} +{% load asset_tags %} +{% load common_tags %} {% block form %}