diff --git a/apps/assets/api/system_user.py b/apps/assets/api/system_user.py index 1be316567..66d62232d 100644 --- a/apps/assets/api/system_user.py +++ b/apps/assets/api/system_user.py @@ -40,7 +40,7 @@ class SystemUserViewSet(BulkModelViewSet): permission_classes = (IsSuperUserOrAppUser,) -class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView): +class SystemUserAuthInfoApi(generics.RetrieveUpdateDestroyAPIView): """ Get system user auth info """ @@ -48,6 +48,11 @@ class SystemUserAuthInfoApi(generics.RetrieveUpdateAPIView): permission_classes = (IsSuperUserOrAppUser,) serializer_class = serializers.SystemUserAuthSerializer + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + instance.clear_auth() + return Response(status=204) + class SystemUserPushApi(generics.RetrieveAPIView): """ diff --git a/apps/assets/models/base.py b/apps/assets/models/base.py index 11afd92cf..cb9bb96ae 100644 --- a/apps/assets/models/base.py +++ b/apps/assets/models/base.py @@ -107,6 +107,7 @@ class AssetUser(models.Model): def clear_auth(self): self._password = '' self._private_key = '' + self._public_key = '' self.save() def auto_gen_auth(self): diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 5ee97af9b..fed8980ed 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -61,10 +61,14 @@ class Node(models.Model): return child def get_children(self): - return self.__class__.objects.filter(key__regex=r'^{}:[0-9]+$'.format(self.key)) + return self.__class__.objects.filter( + key__regex=r'^{}:[0-9]+$'.format(self.key) + ) def get_all_children(self): - return self.__class__.objects.filter(key__startswith='{}:'.format(self.key)) + return self.__class__.objects.filter( + key__startswith='{}:'.format(self.key) + ) def get_family(self): children = list(self.get_all_children()) diff --git a/apps/assets/templates/assets/system_user_detail.html b/apps/assets/templates/assets/system_user_detail.html index cc686a62b..9a7bc255a 100644 --- a/apps/assets/templates/assets/system_user_detail.html +++ b/apps/assets/templates/assets/system_user_detail.html @@ -107,14 +107,14 @@
-
+
{% trans 'Quick update' %}
- + - {% if system_user.auto_push %} + - {% endif %} + + + + + + {# #} {# #} {#
{% trans 'Auto push' %}: @@ -130,8 +130,8 @@
{% trans 'Push system user now' %}: @@ -139,8 +139,8 @@
{% trans 'Test assets connective' %}: @@ -149,6 +149,15 @@
{% trans 'Clear auth' %}: + + + +
{% trans 'Change auth period' %}:#} @@ -239,6 +248,7 @@ $(document).ready(function () { if($('#id_protocol_type').text() === 'rdp'){ $('.only-ssh').addClass('hidden') } + $(".panel-body .table tr:visible:first").addClass('no-borders-tr'); $('.select2').select2() .on('select2:select', function(evt) { var data = evt.params.data; @@ -321,6 +331,13 @@ $(document).ready(function () { success: success, flash_message: false }); +}).on('click', '.btn-clear-auth', function () { + var the_url = '{% url "api-assets:system-user-auth-info" pk=system_user.id %}'; + APIUpdateAttr({ + url: the_url, + method: 'DELETE', + success_message: "{% trans 'Clear auth' %}" + " {% trans 'success' %}" + }); }) {% endblock %} diff --git a/apps/i18n/zh/LC_MESSAGES/django.mo b/apps/i18n/zh/LC_MESSAGES/django.mo index 36220e321..6b02da8fa 100644 Binary files a/apps/i18n/zh/LC_MESSAGES/django.mo and b/apps/i18n/zh/LC_MESSAGES/django.mo differ diff --git a/apps/i18n/zh/LC_MESSAGES/django.po b/apps/i18n/zh/LC_MESSAGES/django.po index 8f95f9291..196c3fb36 100644 --- a/apps/i18n/zh/LC_MESSAGES/django.po +++ b/apps/i18n/zh/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Jumpserver 0.3.3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-05-08 17:24+0800\n" +"POT-Creation-Date: 2018-05-17 11:32+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: ibuler \n" "Language-Team: Jumpserver team\n" @@ -17,22 +17,22 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: assets/api/node.py:96 +#: assets/api/node.py:106 msgid "New node {}" msgstr "新节点 {}" -#: assets/api/node.py:225 +#: assets/api/node.py:242 msgid "更新节点资产硬件信息: {}" msgstr "" -#: assets/api/node.py:238 +#: assets/api/node.py:255 msgid "测试节点下资产是否可连接: {}" msgstr "" #: assets/forms/asset.py:24 assets/models/asset.py:66 assets/models/user.py:103 #: assets/templates/assets/asset_detail.html:183 #: assets/templates/assets/asset_detail.html:191 -#: assets/templates/assets/system_user_detail.html:166 perms/models.py:33 +#: assets/templates/assets/system_user_detail.html:175 perms/models.py:33 msgid "Nodes" msgstr "节点管理" @@ -438,7 +438,7 @@ msgstr "默认资产组" msgid "User" msgstr "用户" -#: assets/models/label.py:18 assets/models/node.py:15 +#: assets/models/label.py:18 assets/models/node.py:18 #: assets/templates/assets/label_list.html:15 common/models.py:27 msgid "Value" msgstr "值" @@ -535,7 +535,7 @@ msgstr "测试系统用户可连接性: {}" msgid "定期测试系统用户可连接性: {}" msgstr "" -#: assets/tasks.py:401 +#: assets/tasks.py:402 msgid "推送系统用户到入资产: {}" msgstr "" @@ -660,7 +660,7 @@ msgstr "重置" #: common/templates/common/ldap_setting.html:60 #: common/templates/common/terminal_setting.html:103 #: perms/templates/perms/asset_permission_create_update.html:70 -#: terminal/templates/terminal/session_list.html:120 +#: terminal/templates/terminal/session_list.html:124 #: terminal/templates/terminal/terminal_update.html:48 #: users/templates/users/_user.html:47 #: users/templates/users/forgot_password.html:44 @@ -782,8 +782,8 @@ msgstr "选择节点" #: assets/templates/assets/admin_user_detail.html:100 #: assets/templates/assets/asset_detail.html:200 -#: assets/templates/assets/asset_list.html:634 -#: assets/templates/assets/system_user_detail.html:183 +#: assets/templates/assets/asset_list.html:636 +#: assets/templates/assets/system_user_detail.html:192 #: assets/templates/assets/system_user_list.html:138 templates/_modal.html:22 #: terminal/templates/terminal/session_detail.html:108 #: users/templates/users/user_detail.html:362 @@ -963,19 +963,19 @@ msgstr "仅显示当前节点资产" msgid "Displays all child node assets" msgstr "显示所有子节点资产" -#: assets/templates/assets/asset_list.html:215 +#: assets/templates/assets/asset_list.html:217 msgid "Create node failed" msgstr "创建节点失败" -#: assets/templates/assets/asset_list.html:227 +#: assets/templates/assets/asset_list.html:229 msgid "Have child node, cancel" msgstr "存在子节点,不能删除" -#: assets/templates/assets/asset_list.html:229 +#: assets/templates/assets/asset_list.html:231 msgid "Have assets, cancel" msgstr "存在资产,不能删除" -#: assets/templates/assets/asset_list.html:629 +#: assets/templates/assets/asset_list.html:631 #: assets/templates/assets/system_user_list.html:133 #: users/templates/users/user_detail.html:357 #: users/templates/users/user_detail.html:382 @@ -984,20 +984,20 @@ msgstr "存在资产,不能删除" msgid "Are you sure?" msgstr "你确认吗?" -#: assets/templates/assets/asset_list.html:630 +#: assets/templates/assets/asset_list.html:632 msgid "This will delete the selected assets !!!" msgstr "删除选择资产" -#: assets/templates/assets/asset_list.html:638 +#: assets/templates/assets/asset_list.html:640 msgid "Asset Deleted." msgstr "已被删除" -#: assets/templates/assets/asset_list.html:639 -#: assets/templates/assets/asset_list.html:644 +#: assets/templates/assets/asset_list.html:641 +#: assets/templates/assets/asset_list.html:646 msgid "Asset Delete" msgstr "删除" -#: assets/templates/assets/asset_list.html:643 +#: assets/templates/assets/asset_list.html:645 msgid "Asset Deleting failed." msgstr "删除失败" @@ -1032,6 +1032,7 @@ msgid "Create gateway" msgstr "创建网关" #: assets/templates/assets/domain_gateway_list.html:87 +#: assets/templates/assets/domain_gateway_list.html:89 #: common/templates/common/email_setting.html:58 #: common/templates/common/ldap_setting.html:58 msgid "Test connection" @@ -1080,10 +1081,23 @@ msgstr "家目录" msgid "Uid" msgstr "Uid" -#: assets/templates/assets/system_user_detail.html:174 +#: assets/templates/assets/system_user_detail.html:153 +#: assets/templates/assets/system_user_detail.html:339 +msgid "Clear auth" +msgstr "清除认证信息" + +#: assets/templates/assets/system_user_detail.html:156 +msgid "Clear" +msgstr "清除" + +#: assets/templates/assets/system_user_detail.html:183 msgid "Add to node" msgstr "添加到节点" +#: assets/templates/assets/system_user_detail.html:339 +msgid "success" +msgstr "成功" + #: assets/templates/assets/system_user_list.html:18 #: assets/views/system_user.py:45 msgid "Create system user" @@ -2113,15 +2127,16 @@ msgstr "时长" msgid "Monitor" msgstr "监控" -#: terminal/templates/terminal/session_list.html:105 +#: terminal/templates/terminal/session_list.html:106 +#: terminal/templates/terminal/session_list.html:108 msgid "Terminate" msgstr "终断" -#: terminal/templates/terminal/session_list.html:116 +#: terminal/templates/terminal/session_list.html:120 msgid "Terminate selected" msgstr "终断所选" -#: terminal/templates/terminal/session_list.html:136 +#: terminal/templates/terminal/session_list.html:140 msgid "Terminate task send, waiting ..." msgstr "终断任务已发送,请等待" diff --git a/apps/perms/api.py b/apps/perms/api.py index be69d6158..8f663a0f4 100644 --- a/apps/perms/api.py +++ b/apps/perms/api.py @@ -6,7 +6,7 @@ from rest_framework.views import APIView, Response from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveUpdateAPIView from rest_framework import viewsets -from common.utils import set_or_append_attr_bulk +from common.utils import set_or_append_attr_bulk, get_object_or_none from users.permissions import IsValidUser, IsSuperUser, IsSuperUserOrAppUser from .utils import AssetPermissionUtil from .models import AssetPermission @@ -147,8 +147,13 @@ class UserGrantedNodeAssetsApi(ListAPIView): user = get_object_or_404(User, id=user_id) else: user = self.request.user - node = get_object_or_404(Node, id=node_id) nodes = AssetPermissionUtil.get_user_nodes_with_assets(user) + node = get_object_or_none(Node, id=node_id) + + if not node: + unnode = [node for node in nodes if node.name == 'Unnode'] + node = unnode[0] if unnode else None + assets = nodes.get(node, []) for asset, system_users in assets.items(): asset.system_users_granted = system_users diff --git a/apps/perms/utils.py b/apps/perms/utils.py index b23b1cb7c..7899cc5cb 100644 --- a/apps/perms/utils.py +++ b/apps/perms/utils.py @@ -13,7 +13,6 @@ logger = get_logger(__file__) class AssetPermissionUtil: - @staticmethod def get_user_permissions(user): return AssetPermission.objects.all().valid().filter(users=user) @@ -122,6 +121,24 @@ class AssetPermissionUtil: nodes[node].update(set(_system_users)) return nodes + @classmethod + def get_user_nodes_inherit_group(cls, user): + nodes = defaultdict(set) + groups = user.groups.all() + for group in groups: + _nodes = cls.get_user_group_nodes(group) + for node, system_users in _nodes.items(): + nodes[node].update(set(system_users)) + return nodes + + @classmethod + def get_user_nodes(cls, user): + nodes = cls.get_user_nodes_direct(user) + nodes_inherit = cls.get_user_nodes_inherit_group(user) + for node, system_users in nodes_inherit.items(): + nodes[node].update(set(system_users)) + return nodes + @classmethod def get_user_nodes_assets_direct(cls, user): assets = defaultdict(set) @@ -164,15 +181,24 @@ class AssetPermissionUtil: :param user: :return: {node: {asset: set(su1, su2)}} """ + from assets.models import Node + unnode = Node(value='Unnode') nodes = defaultdict(dict) + for _node in cls.get_user_nodes(user): + children = _node.get_family() + for node in children: + nodes[node] = defaultdict(set) + nodes[unnode] = defaultdict(set) _assets = cls.get_user_assets(user) for asset, _system_users in _assets.items(): _nodes = asset.get_nodes() + in_node = False for node in _nodes: - if asset in nodes[node]: + if node in nodes: + in_node = True nodes[node][asset].update(_system_users) - else: - nodes[node][asset] = _system_users + if not in_node: + nodes[unnode][asset].update(_system_users) return nodes @classmethod diff --git a/apps/terminal/api.py b/apps/terminal/api.py index c9bb68ed6..924a30dfd 100644 --- a/apps/terminal/api.py +++ b/apps/terminal/api.py @@ -9,6 +9,7 @@ from django.core.cache import cache from django.shortcuts import get_object_or_404, redirect from django.utils import timezone from django.core.files.storage import default_storage +from django.http.response import HttpResponseRedirectBase from django.http import HttpResponseNotFound from django.conf import settings @@ -25,7 +26,7 @@ from .serializers import TerminalSerializer, StatusSerializer, \ SessionSerializer, TaskSerializer, ReplaySerializer from .hands import IsSuperUserOrAppUser, IsAppUser, \ IsSuperUserOrAppUserOrUserReadonly -from .backends import get_command_store, get_multi_command_store, \ +from .backends import get_command_storage, get_multi_command_storage, \ SessionCommandSerializer logger = logging.getLogger(__file__) @@ -227,8 +228,8 @@ class CommandViewSet(viewsets.ViewSet): } """ - command_store = get_command_store() - multi_command_storage = get_multi_command_store() + command_store = get_command_storage() + multi_command_storage = get_multi_command_storage() serializer_class = SessionCommandSerializer permission_classes = (IsSuperUserOrAppUser,) @@ -291,19 +292,20 @@ class SessionReplayViewSet(viewsets.ViewSet): url = default_storage.url(path) return redirect(url) else: - configs = settings.TERMINAL_REPLAY_STORAGE.items() + configs = settings.TERMINAL_REPLAY_STORAGE + configs = [cfg for cfg in configs if cfg['TYPE'] != 'server'] if not configs: return HttpResponseNotFound() - for name, config in configs: - client = jms_storage.init(config) - date = self.session.date_start.strftime('%Y-%m-%d') - file_path = os.path.join(date, str(self.session.id) + '.replay.gz') - target_path = default_storage.base_location + '/' + path - - if client and client.has_file(file_path) and \ - client.download_file(file_path, target_path): - return redirect(default_storage.url(path)) + date = self.session.date_start.strftime('%Y-%m-%d') + file_path = os.path.join(date, str(self.session.id) + '.replay.gz') + target_path = default_storage.base_location + '/' + path + storage = jms_storage.get_multi_object_storage(configs) + ok, err = storage.download(file_path, target_path) + if ok: + return redirect(default_storage.url(path)) + else: + logger.error("Failed download replay file: {}".format(err)) return HttpResponseNotFound() @@ -313,34 +315,14 @@ class SessionReplayV2ViewSet(SessionReplayViewSet): session = None def retrieve(self, request, *args, **kwargs): - session_id = kwargs.get('pk') - self.session = get_object_or_404(Session, id=session_id) - path = self.gen_session_path() + response = super().retrieve(request, *args, **kwargs) data = { 'type': 'guacamole' if self.session.protocol == 'rdp' else 'json', 'src': '', } - - if default_storage.exists(path): - url = default_storage.url(path) - data['src'] = url + if isinstance(response, HttpResponseRedirectBase): + data['src'] = response.url return Response(data) - else: - configs = settings.TERMINAL_REPLAY_STORAGE.items() - if not configs: - return HttpResponseNotFound() - - for name, config in configs: - client = jms_storage.init(config) - date = self.session.date_start.strftime('%Y-%m-%d') - file_path = os.path.join(date, str(self.session.id) + '.replay.gz') - target_path = default_storage.base_location + '/' + path - - if client and client.has_file(file_path) and \ - client.download_file(file_path, target_path): - url = default_storage.url(path) - data['src'] = url - return Response(data) return HttpResponseNotFound() diff --git a/apps/terminal/backends/__init__.py b/apps/terminal/backends/__init__.py index ef1ba56a9..9a1c338f5 100644 --- a/apps/terminal/backends/__init__.py +++ b/apps/terminal/backends/__init__.py @@ -7,19 +7,19 @@ TYPE_ENGINE_MAPPING = { } -def get_command_store(): - params = settings.COMMAND_STORAGE - engine_class = import_module(params['ENGINE']) - storage = engine_class.CommandStore(params) +def get_command_storage(): + config = settings.COMMAND_STORAGE + engine_class = import_module(config['ENGINE']) + storage = engine_class.CommandStore(config) return storage -def get_terminal_command_store(): +def get_terminal_command_storages(): storage_list = {} for name, params in settings.TERMINAL_COMMAND_STORAGE.items(): tp = params['TYPE'] if tp == 'server': - storage = get_command_store() + storage = get_command_storage() else: if not TYPE_ENGINE_MAPPING.get(tp): continue @@ -29,9 +29,9 @@ def get_terminal_command_store(): return storage_list -def get_multi_command_store(): +def get_multi_command_storage(): from .command.multi import CommandStore - storage_list = get_terminal_command_store().values() + storage_list = get_terminal_command_storages().values() storage = CommandStore(storage_list) return storage diff --git a/apps/terminal/backends/command/es.py b/apps/terminal/backends/command/es.py index 9c75fc978..dde8e1e95 100644 --- a/apps/terminal/backends/command/es.py +++ b/apps/terminal/backends/command/es.py @@ -1,41 +1,22 @@ # -*- coding: utf-8 -*- # -from jms_es_sdk import ESStore +from jms_storage.es import ESStorage from .base import CommandBase from .models import AbstractSessionCommand -class CommandStore(CommandBase, ESStore): +class CommandStore(ESStorage, CommandBase): def __init__(self, params): - hosts = params.get('HOSTS', ['http://localhost']) - ESStore.__init__(self, hosts=hosts) - - def save(self, command): - return ESStore.save(self, command) - - def bulk_save(self, commands): - return ESStore.bulk_save(self, commands) + super().__init__(params) def filter(self, date_from=None, date_to=None, user=None, asset=None, system_user=None, input=None, session=None): - data = ESStore.filter( - self, date_from=date_from, date_to=date_to, - user=user, asset=asset, system_user=system_user, - input=input, session=session - ) + data = super().filter(date_from=date_from, date_to=date_to, + user=user, asset=asset, system_user=system_user, + input=input, session=session) return AbstractSessionCommand.from_multi_dict( [item["_source"] for item in data["hits"] if item] ) - - def count(self, date_from=None, date_to=None, - user=None, asset=None, system_user=None, - input=None, session=None): - amount = ESStore.count( - self, date_from=date_from, date_to=date_to, - user=user, asset=asset, system_user=system_user, - input=input, session=session - ) - return amount diff --git a/apps/terminal/serializers.py b/apps/terminal/serializers.py index 8c40315a7..c18c5023d 100644 --- a/apps/terminal/serializers.py +++ b/apps/terminal/serializers.py @@ -9,7 +9,7 @@ from rest_framework_bulk.serializers import BulkListSerializer from common.mixins import BulkSerializerMixin from common.utils import get_object_or_none from .models import Terminal, Status, Session, Task -from .backends import get_multi_command_store +from .backends import get_multi_command_storage class TerminalSerializer(serializers.ModelSerializer): @@ -47,7 +47,7 @@ class TerminalSerializer(serializers.ModelSerializer): class SessionSerializer(serializers.ModelSerializer): command_amount = serializers.SerializerMethodField() - command_store = get_multi_command_store() + command_store = get_multi_command_storage() class Meta: model = Session diff --git a/apps/terminal/templatetags/terminal_tags.py b/apps/terminal/templatetags/terminal_tags.py index cd7120fec..c5643c67b 100644 --- a/apps/terminal/templatetags/terminal_tags.py +++ b/apps/terminal/templatetags/terminal_tags.py @@ -1,10 +1,10 @@ # ~*~ coding: utf-8 ~*~ from django import template -from ..backends import get_multi_command_store +from ..backends import get_multi_command_storage register = template.Library() -command_store = get_multi_command_store() +command_store = get_multi_command_storage() @register.filter diff --git a/apps/terminal/views/command.py b/apps/terminal/views/command.py index 0af0b5bfd..748261414 100644 --- a/apps/terminal/views/command.py +++ b/apps/terminal/views/command.py @@ -9,10 +9,10 @@ from django.utils.translation import ugettext as _ from common.mixins import DatetimeSearchMixin, AdminUserRequiredMixin from ..models import Command from .. import utils -from ..backends import get_multi_command_store +from ..backends import get_multi_command_storage __all__ = ['CommandListView'] -common_storage = get_multi_command_store() +common_storage = get_multi_command_storage() class CommandListView(DatetimeSearchMixin, AdminUserRequiredMixin, ListView): diff --git a/apps/terminal/views/session.py b/apps/terminal/views/session.py index 3b66baff7..71caeae48 100644 --- a/apps/terminal/views/session.py +++ b/apps/terminal/views/session.py @@ -10,7 +10,7 @@ from django.conf import settings from users.utils import AdminUserRequiredMixin from common.mixins import DatetimeSearchMixin from ..models import Session, Command, Terminal -from ..backends import get_multi_command_store +from ..backends import get_multi_command_storage from .. import utils @@ -19,7 +19,7 @@ __all__ = [ 'SessionDetailView', ] -command_store = get_multi_command_store() +command_store = get_multi_command_storage() class SessionListView(AdminUserRequiredMixin, DatetimeSearchMixin, ListView): diff --git a/apps/users/models/group.py b/apps/users/models/group.py index 128fbdbcb..48ed2e949 100644 --- a/apps/users/models/group.py +++ b/apps/users/models/group.py @@ -11,7 +11,7 @@ __all__ = ['UserGroup'] class UserGroup(NoDeleteModelMixin): id = models.UUIDField(default=uuid.uuid4, primary_key=True) - name = models.CharField(max_length=128, verbose_name=_('Name')) + name = models.CharField(max_length=128, unique=True, verbose_name=_('Name')) comment = models.TextField(blank=True, verbose_name=_('Comment')) date_created = models.DateTimeField(auto_now_add=True, null=True, verbose_name=_('Date created')) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index e0ecc634e..af682f5ca 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -40,7 +40,6 @@ itsdangerous==0.24 itypes==1.1.0 Jinja2==2.10 jmespath==0.9.3 -jms-es-sdk kombu==4.0.2 ldap3==2.4 MarkupSafe==1.0 @@ -62,7 +61,7 @@ pytz==2017.3 PyYAML==3.12 redis==2.10.6 requests==2.18.4 -jms-storage==0.0.13 +jms-storage==0.0.17 s3transfer==0.1.13 simplejson==3.13.2 six==1.11.0 diff --git a/utils/clean_duplicate_user_groups.py b/utils/clean_duplicate_user_groups.py new file mode 100644 index 000000000..be8b2d9ec --- /dev/null +++ b/utils/clean_duplicate_user_groups.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# + +import os +import sys +from collections import Counter +import django +from django.db.models import Count + + +if os.path.exists('../apps'): + sys.path.insert(0, '../apps') +elif os.path.exists('./apps'): + sys.path.insert(0, './apps') + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jumpserver.settings") +django.setup() + +from users.models import UserGroup + + +def clean_group(interactive=True): + groups = UserGroup.objects.all() + groups_name_list = groups.values_list('name', flat=True) + groups_with_info = groups.annotate(Count('users'))\ + .annotate(Count('asset_permissions')) + + counter = Counter(groups_name_list) + for name, count in counter.items(): + if count == 0: + continue + groups_duplicate = groups_with_info.filter(name=name) + need_clean_count = groups_duplicate.count() + + for group in groups_duplicate: + need_clean = True + if group.users__count > 0: + need_clean = False + elif group.asset_permissions__count > 0: + need_clean = False + elif need_clean_count == 1: + need_clean = False + + if need_clean: + confirm = True + if interactive: + confirm = False + while True: + confirm = input( + "Delete user group <{}>, create at {}? ([y]/n)".format( + name, group.date_created) + ) + if confirm.lower() == "y": + confirm = True + break + elif confirm.lower() == "n": + confirm = False + break + else: + print("No valid input") + continue + if confirm: + group.delete() + print("Delete success: {}".format(name)) + need_clean_count -= 1 + else: + continue + +if __name__ == '__main__': + clean_group()