diff --git a/apps/assets/api.py b/apps/assets/api.py index f3bfedb5a..bd22c7336 100644 --- a/apps/assets/api.py +++ b/apps/assets/api.py @@ -17,15 +17,15 @@ from rest_framework import generics from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from rest_framework_bulk import ListBulkCreateUpdateDestroyAPIView -from django.shortcuts import get_object_or_404 -from django.db.models import Q from rest_framework.pagination import LimitOffsetPagination +from django.shortcuts import get_object_or_404 +from django.db.models import Q, Count 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, \ @@ -295,3 +295,17 @@ 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"))\ + .annotate(admin_user_count=Count("adminuser")) \ + .annotate(system_user_count=Count("systemuser")) + 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/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..d4be2286e --- /dev/null +++ b/apps/assets/models/label.py @@ -0,0 +1,30 @@ +# -*- 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, 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') + ) + + def __str__(self): + return "{}:{}".format(self.name, self.value) + + class Meta: + db_table = "assets_label" diff --git a/apps/assets/models/user.py b/apps/assets/models/user.py index aa44a539c..ee71afab1 100644 --- a/apps/assets/models/user.py +++ b/apps/assets/models/user.py @@ -30,6 +30,7 @@ class AssetUser(models.Model): _password = models.CharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) _private_key = models.TextField(max_length=4096, blank=True, null=True, verbose_name=_('SSH private key'), validators=[private_key_validator, ]) _public_key = models.TextField(max_length=4096, blank=True, verbose_name=_('SSH public key')) + labels = models.ManyToManyField('assets.Label', blank=True, verbose_name=_("Labels")) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True) date_updated = models.DateTimeField(auto_now=True) diff --git a/apps/assets/serializers.py b/apps/assets/serializers.py index cb939fcc6..7fe14da1b 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 @@ -284,3 +284,44 @@ class MyAssetGroupGrantedSerializer(serializers.ModelSerializer): @staticmethod def get_assets_amount(obj): return len(obj.assets_granted) + + +class LabelSerializer(serializers.ModelSerializer): + asset_count = serializers.SerializerMethodField() + admin_user_count = serializers.SerializerMethodField() + system_user_count = serializers.SerializerMethodField() + + class Meta: + model = Label + fields = '__all__' + list_serializer_class = BulkListSerializer + + @staticmethod + def get_asset_count(obj): + return obj.asset_count + + @staticmethod + def get_admin_user_count(obj): + return obj.admin_user_count + + @staticmethod + def get_system_user_count(obj): + return obj.system_user_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/label_list.html b/apps/assets/templates/assets/label_list.html new file mode 100644 index 000000000..81c5358b2 --- /dev/null +++ b/apps/assets/templates/assets/label_list.html @@ -0,0 +1,72 @@ +{% extends '_base_list.html' %} +{% load i18n static %} +{% block table_search %}{% endblock %} +{% block table_container %} +
+ {% trans "Create label" %} +
+ + + + + + + + + + + + + + +
+ + {% trans 'Name' %}{% trans 'Value' %}{% trans 'Asset' %}{% trans 'Admin user' %}{% trans 'System user' %}{% trans 'Action' %}
+{% endblock %} +{% block content_bottom_left %}{% endblock %} +{% block custom_foot_js %} + +{% endblock %} + + + diff --git a/apps/assets/urls/api_urls.py b/apps/assets/urls/api_urls.py index 503464b31..8c7b77dc7 100644 --- a/apps/assets/urls/api_urls.py +++ b/apps/assets/urls/api_urls.py @@ -12,6 +12,7 @@ router.register(r'v1/assets', api.AssetViewSet, 'asset') router.register(r'v1/clusters', api.ClusterViewSet, 'cluster') router.register(r'v1/admin-user', api.AdminUserViewSet, 'admin-user') router.register(r'v1/system-user', api.SystemUserViewSet, 'system-user') +router.register(r'v1/labels', api.LabelViewSet, 'label') urlpatterns = [ url(r'^v1/assets-bulk/$', api.AssetListUpdateApi.as_view(), name='asset-bulk-update'), diff --git a/apps/assets/urls/views_urls.py b/apps/assets/urls/views_urls.py index e24721c09..09ff322ee 100644 --- a/apps/assets/urls/views_urls.py +++ b/apps/assets/urls/views_urls.py @@ -53,5 +53,6 @@ urlpatterns = [ # url(r'^system-user/(?P[0-9a-zA-Z\-]{36})/asset-group$', views.SystemUserAssetGroupView.as_view(), # name='system-user-asset-group'), + url(r'^label/$', views.LabelListView.as_view(), name='label-list'), ] diff --git a/apps/assets/views/__init__.py b/apps/assets/views/__init__.py index 883120b36..112a46d0d 100644 --- a/apps/assets/views/__init__.py +++ b/apps/assets/views/__init__.py @@ -4,4 +4,5 @@ from .group import * from .cluster import * from .system_user import * from .admin_user import * +from .label import * diff --git a/apps/assets/views/label.py b/apps/assets/views/label.py new file mode 100644 index 000000000..375c500a5 --- /dev/null +++ b/apps/assets/views/label.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# + +from django.views.generic import ListView, TemplateView, CreateView, \ + UpdateView, DeleteView, DetailView +from django.utils.translation import ugettext_lazy as _ + + +from common.mixins import AdminUserRequiredMixin + + +__all__ = ( + "LabelListView", "LabelCreateView", "LabelUpdateView", + "LabelDetailView", "LabelDeleteView", +) + + +class LabelListView(AdminUserRequiredMixin, TemplateView): + template_name = 'assets/label_list.html' + + def get_context_data(self, **kwargs): + context = { + 'app': _('Assets'), + 'action': _('Label list'), + } + kwargs.update(context) + return super().get_context_data(**kwargs) + + +class LabelCreateView(AdminUserRequiredMixin, CreateView): + pass + + +class LabelUpdateView(AdminUserRequiredMixin, UpdateView): + pass + + +class LabelDetailView(AdminUserRequiredMixin, DetailView): + pass + + +class LabelDeleteView(AdminUserRequiredMixin, DeleteView): + pass diff --git a/apps/templates/_nav.html b/apps/templates/_nav.html index 39b14add8..8bc795c9c 100644 --- a/apps/templates/_nav.html +++ b/apps/templates/_nav.html @@ -24,6 +24,7 @@
  • {% trans 'Cluster' %}
  • {% trans 'Admin user' %}
  • {% trans 'System user' %}
  • +
  • {% trans 'Label' %}