Unverified Commit 8cd8f41c authored by 老广's avatar 老广 Committed by GitHub

Dev beta (#3167)

* [Update] 添加loading

* [Update] 修改避免游离资产

* [Update] 修改nodes

* [Update] 修改支持未分组
parent 1fe18e80
...@@ -13,8 +13,6 @@ ...@@ -13,8 +13,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import time
from rest_framework import generics from rest_framework import generics
from rest_framework.serializers import ValidationError from rest_framework.serializers import ValidationError
from rest_framework.views import APIView from rest_framework.views import APIView
......
...@@ -7,5 +7,7 @@ class AssetsConfig(AppConfig): ...@@ -7,5 +7,7 @@ class AssetsConfig(AppConfig):
name = 'assets' name = 'assets'
def ready(self): def ready(self):
from . import signals_handler
super().ready() super().ready()
from . import signals_handler
from .models import Node
Node.initial_some_nodes()
...@@ -11,7 +11,7 @@ from django.utils.translation import ugettext ...@@ -11,7 +11,7 @@ from django.utils.translation import ugettext
from django.core.cache import cache from django.core.cache import cache
from orgs.mixins.models import OrgModelMixin, OrgManager from orgs.mixins.models import OrgModelMixin, OrgManager
from orgs.utils import set_current_org, get_current_org from orgs.utils import set_current_org, get_current_org, tmp_to_root_org, tmp_to_org
from orgs.models import Organization from orgs.models import Organization
...@@ -25,37 +25,42 @@ class NodeQuerySet(models.QuerySet): ...@@ -25,37 +25,42 @@ class NodeQuerySet(models.QuerySet):
class TreeMixin: class TreeMixin:
tree_created_time = None tree_created_time = None
tree_updated_time_cache_key = 'NODE_TREE_CREATED_AT' tree_updated_time_cache_key = 'NODE_TREE_UPDATED_AT'
tree_update_time_cache_time = 3600 tree_cache_time = 3600
tree_assets_cache_key = 'NODE_TREE_ASSETS_UPDATED_AT'
tree_assets_created_time = None
_tree_service = None _tree_service = None
@classmethod @classmethod
def tree(cls): def tree(cls):
# Todo: 有待优化, 因为每次刷新都会导致其他节点的tree失效 完成
# TOdo: 游离的资产,在树上显示的数量不对
# Todo: ungroup node
# Todo: api key页面有bug 完成
from ..utils import TreeService from ..utils import TreeService
tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0) tree_updated_time = cache.get(cls.tree_updated_time_cache_key, 0)
if not cls.tree_created_time or \ if not cls.tree_created_time or \
tree_updated_time > cls.tree_created_time: tree_updated_time > cls.tree_created_time:
print("New tree")
tree = TreeService.new() tree = TreeService.new()
cls.tree_created_time = time.time() cls.tree_created_time = time.time()
cls.tree_assets_created_time = time.time()
cls._tree_service = tree cls._tree_service = tree
return tree return tree
node_assets_updated_time = cache.get(cls.tree_assets_cache_key, 0)
if not cls.tree_assets_created_time or \
node_assets_updated_time > cls.tree_assets_created_time:
cls._tree_service.init_assets_async()
return cls._tree_service return cls._tree_service
@classmethod @classmethod
def expire_cache_tree(cls): def refresh_tree(cls):
key = cls.tree_updated_time_cache_key key = cls.tree_updated_time_cache_key
ttl = cls.tree_update_time_cache_time ttl = cls.tree_cache_time
value = time.time() value = time.time()
cache.set(key, value, ttl) cache.set(key, value, ttl)
@classmethod @classmethod
def refresh_tree(cls): def refresh_node_assets(cls):
cls.expire_cache_tree() key = cls.tree_assets_cache_key
ttl = cls.tree_cache_time
value = time.time()
cache.set(key, value, ttl)
@property @property
def _tree(self): def _tree(self):
...@@ -183,6 +188,31 @@ class FamilyMixin: ...@@ -183,6 +188,31 @@ class FamilyMixin:
key_list.pop() key_list.pop()
return keys return keys
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def get_next_child_preset_name(self):
name = ugettext("New node")
values = [
child.value[child.value.rfind(' '):]
for child in self.get_children()
if child.value.startswith(name)
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
def create_child(self, value, _id=None):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(
id=_id, key=child_key, value=value
)
return child
class FullValueMixin: class FullValueMixin:
_full_value = None _full_value = None
...@@ -246,7 +276,85 @@ class NodeAssetsMixin: ...@@ -246,7 +276,85 @@ class NodeAssetsMixin:
return Asset.objects.filter(nodes__key__regex=pattern) return Asset.objects.filter(nodes__key__regex=pattern)
class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin): class SomeNodesMixin:
key = ''
default_key = '1'
default_value = 'Default'
ungrouped_key = '-10'
ungrouped_value = _('ungrouped')
empty_key = '-11'
empty_value = _("empty")
def is_default_node(self):
return self.key == self.default_key
def is_org_root(self):
if self.key.isdigit():
return True
else:
return False
@classmethod
def create_org_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
ori_org = get_current_org()
with transaction.atomic():
if not ori_org.is_real():
return cls.default_node()
set_current_org(Organization.root())
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
if not org_nodes_roots_keys:
org_nodes_roots_keys = ['1']
key = max([int(k) for k in org_nodes_roots_keys])
key = str(key + 1) if key != 0 else '2'
set_current_org(ori_org)
root = cls.objects.create(key=key, value=ori_org.name)
return root
@classmethod
def org_root(cls):
root = cls.objects.filter(key__regex=r'^[0-9]+$')
if root:
return root[0]
else:
return cls.create_org_root_node()
@classmethod
def ungrouped_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.ungrouped_key}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.ungrouped_key
)
return obj
@classmethod
def empty_node(cls):
with tmp_to_org(Organization.system()):
defaults = {'value': cls.empty_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.empty_key
)
return obj
@classmethod
def default_node(cls):
with tmp_to_org(Organization.default()):
defaults = {'value': cls.default_value}
obj, created = cls.objects.get_or_create(
defaults=defaults, key=cls.default_key,
)
return obj
@classmethod
def initial_some_nodes(cls):
cls.default_node()
cls.empty_node()
cls.ungrouped_node()
class Node(OrgModelMixin, SomeNodesMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1' key = models.CharField(unique=True, max_length=64, verbose_name=_("Key")) # '1:1:1:1'
value = models.CharField(max_length=128, verbose_name=_("Value")) value = models.CharField(max_length=128, verbose_name=_("Value"))
...@@ -290,75 +398,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi ...@@ -290,75 +398,13 @@ class Node(OrgModelMixin, TreeMixin, FamilyMixin, FullValueMixin, NodeAssetsMixi
def level(self): def level(self):
return len(self.key.split(':')) return len(self.key.split(':'))
def get_next_child_key(self):
mark = self.child_mark
self.child_mark += 1
self.save()
return "{}:{}".format(self.key, mark)
def get_next_child_preset_name(self):
name = ugettext("New node")
values = [
child.value[child.value.rfind(' '):]
for child in self.get_children()
if child.value.startswith(name)
]
values = [int(value) for value in values if value.strip().isdigit()]
count = max(values) + 1 if values else 1
return '{} {}'.format(name, count)
def create_child(self, value, _id=None):
with transaction.atomic():
child_key = self.get_next_child_key()
child = self.__class__.objects.create(
id=_id, key=child_key, value=value
)
return child
@classmethod @classmethod
def refresh_nodes(cls): def refresh_nodes(cls):
cls.refresh_tree() cls.refresh_tree()
def is_default_node(self):
return self.key == '1'
def is_org_root(self):
if self.key.isdigit():
return True
else:
return False
@classmethod
def create_org_root_node(cls):
# 如果使用current_org 在set_current_org时会死循环
ori_org = get_current_org()
with transaction.atomic():
if not ori_org.is_real():
return cls.default_node()
set_current_org(Organization.root())
org_nodes_roots = cls.objects.filter(key__regex=r'^[0-9]+$')
org_nodes_roots_keys = org_nodes_roots.values_list('key', flat=True)
if not org_nodes_roots_keys:
org_nodes_roots_keys = ['1']
key = max([int(k) for k in org_nodes_roots_keys])
key = str(key + 1) if key != 0 else '2'
set_current_org(ori_org)
root = cls.objects.create(key=key, value=ori_org.name)
return root
@classmethod @classmethod
def org_root(cls): def refresh_assets(cls):
root = cls.objects.filter(key__regex=r'^[0-9]+$') cls.refresh_node_assets()
if root:
return root[0]
else:
return cls.create_org_root_node()
@classmethod
def default_node(cls):
defaults = {'value': 'Default'}
obj, created = cls.objects.get_or_create(defaults=defaults, key='1')
return obj
def as_tree_node(self): def as_tree_node(self):
from common.tree import TreeNode from common.tree import TreeNode
......
...@@ -14,7 +14,9 @@ __all__ = [ ...@@ -14,7 +14,9 @@ __all__ = [
class NodeSerializer(BulkOrgResourceModelSerializer): class NodeSerializer(BulkOrgResourceModelSerializer):
name = serializers.ReadOnlyField(source='value') name = serializers.ReadOnlyField(source='value')
value = serializers.CharField(required=False, allow_blank=True, allow_null=True, label=_("value")) value = serializers.CharField(
required=False, allow_blank=True, allow_null=True, label=_("value")
)
class Meta: class Meta:
model = Node model = Node
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
# #
from collections import defaultdict from collections import defaultdict
from django.db.models.signals import ( from django.db.models.signals import (
post_save, m2m_changed, pre_delete, pre_save, pre_init, post_init post_save, m2m_changed, post_delete
) )
from django.db.models.aggregates import Count
from django.dispatch import receiver from django.dispatch import receiver
from common.utils import get_logger from common.utils import get_logger
...@@ -29,29 +30,36 @@ def test_asset_conn_on_created(asset): ...@@ -29,29 +30,36 @@ def test_asset_conn_on_created(asset):
test_asset_connectivity_util.delay([asset]) test_asset_connectivity_util.delay([asset])
@receiver(post_save, sender=Asset, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=Asset)
@on_transaction_commit @on_transaction_commit
def on_asset_created_or_update(sender, instance=None, created=False, **kwargs): def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
""" """
当资产创建时,更新硬件信息,更新可连接性 当资产创建时,更新硬件信息,更新可连接性
确保资产必须属于一个节点
""" """
if created: if created:
logger.info("Asset `{}` create signal received".format(instance)) logger.info("Asset create signal recv: {}".format(instance))
# 获取资产硬件信息 # 获取资产硬件信息
update_asset_hardware_info_on_created(instance) update_asset_hardware_info_on_created(instance)
test_asset_conn_on_created(instance) test_asset_conn_on_created(instance)
# 确保资产存在一个节点
has_node = instance.nodes.all().exists()
if not has_node:
instance.nodes.add(Node.org_root())
@receiver(pre_delete, sender=Asset, dispatch_uid="my_unique_identifier")
@receiver(post_delete, sender=Asset)
def on_asset_delete(sender, instance=None, **kwargs): def on_asset_delete(sender, instance=None, **kwargs):
""" """
当资产删除时,刷新节点,节点中存在节点和资产的关系 当资产删除时,刷新节点,节点中存在节点和资产的关系
""" """
Node.refresh_nodes() logger.debug("Asset delete signal recv: {}".format(instance))
Node.refresh_assets()
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier") @receiver(post_save, sender=SystemUser, dispatch_uid="jms")
def on_system_user_update(sender, instance=None, created=True, **kwargs): def on_system_user_update(sender, instance=None, created=True, **kwargs):
""" """
当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上, 当系统用户更新时,可能更新了秘钥,用户名等,这时要自动推送系统用户到资产上,
...@@ -60,61 +68,126 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs): ...@@ -60,61 +68,126 @@ def on_system_user_update(sender, instance=None, created=True, **kwargs):
关联到上面 关联到上面
""" """
if instance and not created: if instance and not created:
logger.info("System user `{}` update signal received".format(instance)) logger.info("System user update signal recv: {}".format(instance))
assets = instance.assets.all().valid() assets = instance.assets.all().valid()
push_system_user_to_assets.delay(instance, assets) push_system_user_to_assets.delay(instance, assets)
@receiver(m2m_changed, sender=SystemUser.assets.through, dispatch_uid="my_unique_identifier") @receiver(m2m_changed, sender=SystemUser.assets.through)
def on_system_user_assets_change(sender, instance=None, **kwargs): def on_system_user_assets_change(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
""" """
当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中 当系统用户和资产关系发生变化时,应该重新推送系统用户到新添加的资产中
""" """
if instance and kwargs["action"] == "post_add": if action != "post_add":
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) return
push_system_user_to_assets.delay(instance, assets) logger.debug("System user assets change signal recv: {}".format(instance))
queryset = model.objects.filter(pk__in=pk_set)
if model == Asset:
@receiver(m2m_changed, sender=SystemUser.nodes.through, dispatch_uid="my_unique_identifier") system_users = [instance]
def on_system_user_nodes_change(sender, instance=None, **kwargs): assets = queryset
else:
system_users = queryset
assets = [instance]
for system_user in system_users:
push_system_user_to_assets.delay(system_user, assets)
@receiver(m2m_changed, sender=SystemUser.nodes.through)
def on_system_user_nodes_change(sender, instance=None, action=None, model=None, pk_set=None, **kwargs):
"""
当系统用户和节点关系发生变化时,应该将节点下资产关联到新的系统用户上
"""
if action != "post_add":
return
logger.info("System user `{}` nodes update signal recv".format(instance))
queryset = model.objects.filter(pk__in=pk_set)
if model == Node:
nodes_keys = queryset.values_list('key', flat=True)
system_users = [instance]
else:
nodes_keys = [instance.key]
system_users = queryset
assets = Node.get_nodes_all_assets(nodes_keys)
for system_user in system_users:
system_user.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_nodes_change(sender, instance=None, action='', **kwargs):
""" """
当系统用户和节点关系发生变化时,应该将节点关联到新的系统用户上 资产节点发生变化时,刷新节点
""" """
if instance and kwargs["action"] == "post_add": if action.startswith('post'):
logger.info("System user `{}` nodes update signal received".format(instance)) logger.debug("Asset nodes change signal recv: {}".format(instance))
nodes_keys = kwargs['model'].objects.filter( Node.refresh_assets()
pk__in=kwargs['pk_set']
).values_list('key', flat=True)
assets = Node.get_nodes_all_assets(nodes_keys)
instance.assets.add(*tuple(assets))
@receiver(m2m_changed, sender=Asset.nodes.through, dispatch_uid="my_unique_identifier") @receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_nodes_changed(sender, instance=None, **kwargs): def on_asset_nodes_add(sender, instance=None, action='', model=None, pk_set=None, **kwargs):
""" """
当资产的节点发生变化时,或者 当节点的资产关系发生变化时, 当资产的节点发生变化时,或者 当节点的资产关系发生变化时,
节点下新增的资产,添加到节点关联的系统用户中 节点下新增的资产,添加到节点关联的系统用户中
并刷新节点 """
""" if action != "post_add":
if isinstance(instance, Asset): return
logger.debug("Asset nodes change signal received: {}".format(instance)) logger.debug("Assets node add signal recv: {}".format(action))
# 节点资产发生变化时,将资产关联到节点关联的系统用户 queryset = model.objects.filter(pk__in=pk_set)
if kwargs['action'] == 'post_add': if model == Node:
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = queryset
system_users_assets = defaultdict(set) assets = [instance]
system_users = SystemUser.objects.filter(nodes__in=nodes) else:
for system_user in system_users: nodes = [instance]
system_users_assets[system_user].add(instance) assets = queryset
for system_user, assets in system_users_assets.items(): # 节点资产发生变化时,将资产关联到节点关联的系统用户, 只关注新增的
system_user.assets.add(*tuple(assets)) system_users_assets = defaultdict(set)
if isinstance(instance, Node): system_users = SystemUser.objects.filter(nodes__in=nodes)
logger.debug("Node assets change signal received: {}".format(instance)) for system_user in system_users:
system_users_assets[system_user].update(set(assets))
Node.refresh_nodes() for system_user, _assets in system_users_assets.items():
system_user.assets.add(*tuple(_assets))
@receiver(m2m_changed, sender=Asset.nodes.through)
def on_asset_nodes_remove(sender, instance=None, action='', model=None,
pk_set=None, **kwargs):
@receiver(post_save, sender=Node) """
def on_node_update_or_created(sender, instance=None, created=False, **kwargs): 监控资产删除节点关系, 或节点删除资产,避免产生游离资产
"""
if action not in ["post_remove", "pre_clear", "post_clear"]:
return
if action == "pre_clear":
if model == Node:
instance._nodes = list(instance.nodes.all())
else:
instance._assets = list(instance.assets.all())
return
logger.debug("Assets node remove signal recv: {}".format(action))
if action == "post_remove":
queryset = model.objects.filter(pk__in=pk_set)
else:
if model == Node:
queryset = instance._nodes
else:
queryset = instance._assets
if model == Node:
assets = [instance]
else:
assets = queryset
if isinstance(assets, list):
assets_not_has_node = []
for asset in assets:
if asset.nodes.all().count() == 0:
assets_not_has_node.append(asset.id)
else:
assets_not_has_node = assets.annotate(nodes_count=Count('nodes'))\
.filter(nodes_count=0).values_list('id', flat=True)
Node.org_root().assets.add(*tuple(assets_not_has_node))
@receiver([post_save, post_delete], sender=Node)
def on_node_update_or_created(sender, **kwargs):
# 刷新节点 # 刷新节点
Node.refresh_nodes() Node.refresh_nodes()
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
<div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px"> <div class="ibox-content mailbox-content" style="padding-top: 0;padding-left: 1px">
<div class="file-manager" id="tree-node-id"> <div class="file-manager" id="tree-node-id">
<div id="{% block treeID %}nodeTree{% endblock %}" class="ztree"> <div id="{% block treeID %}nodeTree{% endblock %}" class="ztree">
{% trans 'Loading' %} ...
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
...@@ -124,7 +124,6 @@ ...@@ -124,7 +124,6 @@
</div> </div>
</div> </div>
{% include 'assets/_node_tree.html' %}
{% include 'assets/_asset_update_modal.html' %} {% include 'assets/_asset_update_modal.html' %}
{% include 'assets/_asset_import_modal.html' %} {% include 'assets/_asset_import_modal.html' %}
{% include 'assets/_asset_list_modal.html' %} {% include 'assets/_asset_list_modal.html' %}
......
...@@ -59,6 +59,8 @@ class TreeService(Tree): ...@@ -59,6 +59,8 @@ class TreeService(Tree):
tag_sep = ' / ' tag_sep = ' / '
cache_key = '_NODE_FULL_TREE' cache_key = '_NODE_FULL_TREE'
cache_time = 3600 cache_time = 3600
has_empty_node = False
has_ungrouped_node = False
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
...@@ -119,9 +121,9 @@ class TreeService(Tree): ...@@ -119,9 +121,9 @@ class TreeService(Tree):
return [self.get_node(i, deep=deep) for i in ancestor_ids] return [self.get_node(i, deep=deep) for i in ancestor_ids]
def get_node_full_tag(self, nid): def get_node_full_tag(self, nid):
ancestors = self.ancestors(nid) ancestors = self.ancestors(nid, with_self=True)
ancestors.reverse() ancestors.reverse()
return self.tag_sep.join(n.tag for n in ancestors) return self.tag_sep.join([n.tag for n in ancestors])
def get_family(self, nid, deep=False): def get_family(self, nid, deep=False):
ancestors = self.ancestors(nid, with_self=False, deep=deep) ancestors = self.ancestors(nid, with_self=False, deep=deep)
......
...@@ -290,10 +290,10 @@ LOGGING = { ...@@ -290,10 +290,10 @@ LOGGING = {
'handlers': ['syslog'], 'handlers': ['syslog'],
'level': 'INFO' 'level': 'INFO'
}, },
'django.db': { # 'django.db': {
'handlers': ['console', 'file'], # 'handlers': ['console', 'file'],
'level': 'DEBUG' # 'level': 'DEBUG'
} # }
} }
} }
......
...@@ -64,8 +64,11 @@ class OrgModelMixin(models.Model): ...@@ -64,8 +64,11 @@ class OrgModelMixin(models.Model):
sep = '@' sep = '@'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if current_org is not None and current_org.is_real(): org = get_current_org()
self.org_id = current_org.id if org is not None and (org.is_real() or org.is_system()):
self.org_id = org.id
elif org is not None and org.is_default():
self.org_id = ''
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@property @property
......
...@@ -21,6 +21,8 @@ class Organization(models.Model): ...@@ -21,6 +21,8 @@ class Organization(models.Model):
ROOT_NAME = 'ROOT' ROOT_NAME = 'ROOT'
DEFAULT_ID = 'DEFAULT' DEFAULT_ID = 'DEFAULT'
DEFAULT_NAME = 'DEFAULT' DEFAULT_NAME = 'DEFAULT'
SYSTEM_ID = '00000000-0000-0000-0000-000000000002'
SYSTEM_NAME = 'SYSTEM'
_user_admin_orgs = None _user_admin_orgs = None
class Meta: class Meta:
...@@ -55,6 +57,8 @@ class Organization(models.Model): ...@@ -55,6 +57,8 @@ class Organization(models.Model):
return cls.default() return cls.default()
elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]: elif id_or_name in [cls.ROOT_ID, cls.ROOT_NAME]:
return cls.root() return cls.root()
elif id_or_name in [cls.SYSTEM_ID, cls.SYSTEM_NAME]:
return cls.system()
try: try:
if is_uuid(id_or_name): if is_uuid(id_or_name):
...@@ -89,7 +93,7 @@ class Organization(models.Model): ...@@ -89,7 +93,7 @@ class Organization(models.Model):
return False return False
def is_real(self): def is_real(self):
return self.id not in (self.DEFAULT_NAME, self.ROOT_ID) return self.id not in (self.DEFAULT_NAME, self.ROOT_ID, self.SYSTEM_ID)
@classmethod @classmethod
def get_user_admin_orgs(cls, user): def get_user_admin_orgs(cls, user):
...@@ -111,17 +115,18 @@ class Organization(models.Model): ...@@ -111,17 +115,18 @@ class Organization(models.Model):
def root(cls): def root(cls):
return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME) return cls(id=cls.ROOT_ID, name=cls.ROOT_NAME)
@classmethod
def system(cls):
return cls(id=cls.SYSTEM_ID, name=cls.SYSTEM_NAME)
def is_root(self): def is_root(self):
if self.id is self.ROOT_ID: return self.id is self.ROOT_ID
return True
else:
return False
def is_default(self): def is_default(self):
if self.id is self.DEFAULT_ID: return self.id is self.DEFAULT_ID
return True
else: def is_system(self):
return False return self.id is self.SYSTEM_ID
def change_to(self): def change_to(self):
from .utils import set_current_org from .utils import set_current_org
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from werkzeug.local import LocalProxy from werkzeug.local import LocalProxy
from contextlib import contextmanager
from common.local import thread_local from common.local import thread_local
from .models import Organization from .models import Organization
...@@ -52,4 +53,22 @@ def get_current_org_id_for_serializer(): ...@@ -52,4 +53,22 @@ def get_current_org_id_for_serializer():
return org_id return org_id
@contextmanager
def tmp_to_root_org():
ori_org = get_current_org()
set_to_root_org()
yield
if ori_org is not None:
set_current_org(ori_org)
@contextmanager
def tmp_to_org(org):
ori_org = get_current_org()
set_current_org(org)
yield
if ori_org is not None:
set_current_org(ori_org)
current_org = LocalProxy(get_current_org) current_org = LocalProxy(get_current_org)
...@@ -40,24 +40,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet): ...@@ -40,24 +40,14 @@ class AssetPermissionViewSet(viewsets.ModelViewSet):
return self.serializer_class return self.serializer_class
def filter_valid(self, queryset): def filter_valid(self, queryset):
valid = self.request.query_params.get('is_valid', None) valid_query = self.request.query_params.get('is_valid', None)
if valid is None: if valid_query is None:
return queryset return queryset
if valid in ['0', 'N', 'false', 'False']: invalid = valid_query in ['0', 'N', 'false', 'False']
valid = False if invalid:
queryset = queryset.invalid()
else: else:
valid = True queryset = queryset.valid()
now = timezone.now()
if valid:
queryset = queryset.filter(is_active=True).filter(
date_start__lt=now, date_expired__gt=now,
)
else:
queryset = queryset.filter(
Q(is_active=False) |
Q(date_start__gt=now) |
Q(date_expired__lt=now)
)
return queryset return queryset
def filter_system_user(self, queryset): def filter_system_user(self, queryset):
......
...@@ -15,10 +15,6 @@ from orgs.utils import set_to_root_org ...@@ -15,10 +15,6 @@ from orgs.utils import set_to_root_org
from ..utils import ( from ..utils import (
ParserNode, AssetPermissionUtilV2 ParserNode, AssetPermissionUtilV2
) )
from .mixin import (
UserPermissionCacheMixin, GrantAssetsMixin, NodesWithUngroupMixin
)
from .. import const
from ..hands import User, Asset, Node, SystemUser, NodeSerializer from ..hands import User, Asset, Node, SystemUser, NodeSerializer
from .. import serializers from .. import serializers
from ..models import Action from ..models import Action
...@@ -66,8 +62,8 @@ class UserGrantedAssetsApi(UserPermissionMixin, ListAPIView): ...@@ -66,8 +62,8 @@ class UserGrantedAssetsApi(UserPermissionMixin, ListAPIView):
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.AssetGrantedSerializer serializer_class = serializers.AssetGrantedSerializer
only_fields = serializers.AssetGrantedSerializer.Meta.only_fields only_fields = serializers.AssetGrantedSerializer.Meta.only_fields
filter_fields = ['hostname', 'ip'] filter_fields = ['hostname', 'ip', 'id', 'comment']
search_fields = filter_fields search_fields = ['hostname', 'ip', 'comment']
def filter_by_nodes(self, queryset): def filter_by_nodes(self, queryset):
node_id = self.request.query_params.get("node") node_id = self.request.query_params.get("node")
...@@ -109,12 +105,21 @@ class UserGrantedNodesApi(UserPermissionMixin, ListAPIView): ...@@ -109,12 +105,21 @@ class UserGrantedNodesApi(UserPermissionMixin, ListAPIView):
查询用户授权的所有节点的API 查询用户授权的所有节点的API
""" """
permission_classes = (IsOrgAdminOrAppUser,) permission_classes = (IsOrgAdminOrAppUser,)
serializer_class = serializers.GrantedNodeSerializer serializer_class = serializers.NodeGrantedSerializer
only_fields = NodeSerializer.Meta.only_fields only_fields = NodeSerializer.Meta.only_fields
util = None
def get(self, request, *args, **kwargs):
self.util = AssetPermissionUtilV2(self.obj)
return super().get(request, *args, **kwargs)
def get_serializer_context(self):
context = super().get_serializer_context()
context["tree"] = self.util.user_tree
return context
def get_queryset(self): def get_queryset(self):
util = AssetPermissionUtilV2(self.obj) node_keys = self.util.get_nodes()
node_keys = util.get_nodes()
queryset = Node.objects.filter(key__in=node_keys) queryset = Node.objects.filter(key__in=node_keys)
return queryset return queryset
...@@ -131,7 +136,6 @@ class UserGrantedNodeChildrenApi(UserGrantedNodesApi): ...@@ -131,7 +136,6 @@ class UserGrantedNodeChildrenApi(UserGrantedNodesApi):
system_user_id = self.request.query_params.get("system_user") system_user_id = self.request.query_params.get("system_user")
self.util = AssetPermissionUtilV2(self.obj) self.util = AssetPermissionUtilV2(self.obj)
if system_user_id: if system_user_id:
system_user = get_object_or_404(SystemUser, id=system_user_id)
self.util.filter_permissions(system_users=system_user_id) self.util.filter_permissions(system_users=system_user_id)
self.tree = self.util.get_user_tree() self.tree = self.util.get_user_tree()
......
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
from django.utils.translation import ugettext_lazy as _
UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002" UNGROUPED_NODE_ID = "00000000-0000-0000-0000-000000000002"
UNGROUPED_NODE_KEY = '-2'
UNGROUPED_NODE_VALUE = _("Ungrouped")
EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003" EMPTY_NODE_ID = "00000000-0000-0000-0000-000000000003"
EMPTY_NODE_KEY = "1:-2" EMPTY_NODE_KEY = "-3"
EMPTY_NODE_VALUE = _("Empty")
...@@ -5,7 +5,7 @@ from django.db import models ...@@ -5,7 +5,7 @@ from django.db import models
from django.db.models import Q from django.db.models import Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.utils import date_expired_default, set_or_append_attr_bulk from common.utils import date_expired_default
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from assets.models import Asset, SystemUser, Node from assets.models import Asset, SystemUser, Node
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import uuid import uuid
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db import models from django.db import models
from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
...@@ -24,6 +25,18 @@ class BasePermissionQuerySet(models.QuerySet): ...@@ -24,6 +25,18 @@ class BasePermissionQuerySet(models.QuerySet):
return self.active().filter(date_start__lt=timezone.now()) \ return self.active().filter(date_start__lt=timezone.now()) \
.filter(date_expired__gt=timezone.now()) .filter(date_expired__gt=timezone.now())
def inactive(self):
return self.filter(is_active=False)
def invalid(self):
now = timezone.now
q = (
Q(is_active=False) |
Q(date_start__gt=now) |
Q(date_expired__lt=now)
)
return self.filter(q)
class BasePermissionManager(OrgManager): class BasePermissionManager(OrgManager):
def valid(self): def valid(self):
......
...@@ -9,8 +9,8 @@ from assets.serializers import ProtocolsField ...@@ -9,8 +9,8 @@ from assets.serializers import ProtocolsField
from .asset_permission import ActionsField from .asset_permission import ActionsField
__all__ = [ __all__ = [
'GrantedNodeSerializer', 'NodeGrantedSerializer',
'NodeGrantedSerializer', 'AssetGrantedSerializer', 'AssetGrantedSerializer',
'ActionsSerializer', 'AssetSystemUserSerializer', 'ActionsSerializer', 'AssetSystemUserSerializer',
] ]
...@@ -43,36 +43,29 @@ class AssetGrantedSerializer(serializers.ModelSerializer): ...@@ -43,36 +43,29 @@ class AssetGrantedSerializer(serializers.ModelSerializer):
"id", "hostname", "ip", "protocols", "os", 'domain', "id", "hostname", "ip", "protocols", "os", 'domain',
"platform", "comment", "org_id", "platform", "comment", "org_id",
] ]
fields = only_fields fields = only_fields + ['org_name']
read_only_fields = fields read_only_fields = fields
class NodeGrantedSerializer(serializers.ModelSerializer): class NodeGrantedSerializer(serializers.ModelSerializer):
""" assets_amount = serializers.SerializerMethodField()
授权资产组
"""
# assets_granted = AssetGrantedSerializer(many=True, read_only=True)
# assets_amount = serializers.ReadOnlyField()
name = serializers.ReadOnlyField(source='value')
# assets_only_fields = AssetGrantedSerializer.Meta.only_fields
# system_users_only_fields = AssetGrantedSerializer.system_users_only_fields
class Meta:
model = Node
only_fields = ['id', 'key', 'value', "org_id"]
fields = only_fields + ['name']
read_only_fields = fields
class GrantedNodeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Node model = Node
fields = [ fields = [
'id', 'name', 'key', 'value', 'id', 'name', 'key', 'value', 'org_id', "assets_amount"
] ]
read_only_fields = fields read_only_fields = fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tree = self.context.get("tree")
def get_assets_amount(self, obj):
if not self.tree:
return 0
return self.tree.assets_amount(obj.key)
class ActionsSerializer(serializers.Serializer): class ActionsSerializer(serializers.Serializer):
actions = ActionsField(read_only=True) actions = ActionsField(read_only=True)
...@@ -15,25 +15,23 @@ logger = get_logger(__file__) ...@@ -15,25 +15,23 @@ logger = get_logger(__file__)
@on_transaction_commit @on_transaction_commit
def on_permission_created(sender, instance=None, created=False, **kwargs): def on_permission_created(sender, instance=None, created=False, **kwargs):
pass pass
# AssetPermissionUtil.expire_all_cache()
@receiver(post_save, sender=AssetPermission) @receiver(post_save, sender=AssetPermission)
def on_permission_update(sender, **kwargs): def on_permission_update(sender, **kwargs):
pass pass
# AssetPermissionUtil.expire_all_cache()
@receiver(post_delete, sender=AssetPermission) @receiver(post_delete, sender=AssetPermission)
def on_permission_delete(sender, **kwargs): def on_permission_delete(sender, **kwargs):
pass pass
# AssetPermissionUtil.expire_all_cache()
@receiver(m2m_changed, sender=AssetPermission.nodes.through) @receiver(m2m_changed, sender=AssetPermission.nodes.through)
def on_permission_nodes_changed(sender, instance=None, **kwargs): def on_permission_nodes_changed(sender, instance=None, action='', **kwargs):
# AssetPermissionUtil.expire_all_cache() if action != 'post_add':
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add': return
if isinstance(instance, AssetPermission):
logger.debug("Asset permission nodes change signal received") logger.debug("Asset permission nodes change signal received")
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users = instance.system_users.all() system_users = instance.system_users.all()
...@@ -42,9 +40,10 @@ def on_permission_nodes_changed(sender, instance=None, **kwargs): ...@@ -42,9 +40,10 @@ def on_permission_nodes_changed(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=AssetPermission.assets.through) @receiver(m2m_changed, sender=AssetPermission.assets.through)
def on_permission_assets_changed(sender, instance=None, **kwargs): def on_permission_assets_changed(sender, instance=None, action='', **kwargs):
# AssetPermissionUtil.expire_all_cache() if action != 'post_add':
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add': return
if isinstance(instance, AssetPermission):
logger.debug("Asset permission assets change signal received") logger.debug("Asset permission assets change signal received")
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
system_users = instance.system_users.all() system_users = instance.system_users.all()
...@@ -53,13 +52,15 @@ def on_permission_assets_changed(sender, instance=None, **kwargs): ...@@ -53,13 +52,15 @@ def on_permission_assets_changed(sender, instance=None, **kwargs):
@receiver(m2m_changed, sender=AssetPermission.system_users.through) @receiver(m2m_changed, sender=AssetPermission.system_users.through)
def on_permission_system_users_changed(sender, instance=None, **kwargs): def on_permission_system_users_changed(sender, instance=None, action='', **kwargs):
# AssetPermissionUtil.expire_all_cache() if action != 'post_add':
if isinstance(instance, AssetPermission) and kwargs['action'] == 'post_add': return
logger.debug("Asset permission system_users change signal received") if isinstance(instance, AssetPermission):
system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) system_users = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
logger.debug("Asset permission system_users change signal received")
assets = instance.assets.all() assets = instance.assets.all()
nodes = instance.nodes.all() nodes = instance.nodes.all()
for system_user in system_users: for system_user in system_users:
system_user.nodes.add(*tuple(nodes)) system_user.nodes.add(*tuple(nodes))
system_user.assets.add(*tuple(assets)) system_user.assets.add(*tuple(assets))
...@@ -4,6 +4,7 @@ from collections import defaultdict ...@@ -4,6 +4,7 @@ from collections import defaultdict
from functools import reduce from functools import reduce
from django.db.models import Q from django.db.models import Q
from django.conf import settings
from orgs.utils import set_to_root_org from orgs.utils import set_to_root_org
from common.utils import get_logger, timeit from common.utils import get_logger, timeit
...@@ -11,6 +12,7 @@ from common.tree import TreeNode ...@@ -11,6 +12,7 @@ from common.tree import TreeNode
from assets.utils import TreeService from assets.utils import TreeService
from ..models import AssetPermission from ..models import AssetPermission
from ..hands import Node, Asset, SystemUser from ..hands import Node, Asset, SystemUser
from .. import const
logger = get_logger(__file__) logger = get_logger(__file__)
...@@ -129,6 +131,9 @@ class AssetPermissionUtilV2: ...@@ -129,6 +131,9 @@ class AssetPermissionUtilV2:
@timeit @timeit
def add_direct_nodes_to_user_tree(self, user_tree): def add_direct_nodes_to_user_tree(self, user_tree):
"""
将授权规则的节点放到用户树上, 从full tree中粘贴子树
"""
nodes_direct_keys = self.permissions \ nodes_direct_keys = self.permissions \
.exclude(nodes__isnull=True) \ .exclude(nodes__isnull=True) \
.values_list('nodes__key', flat=True) \ .values_list('nodes__key', flat=True) \
...@@ -153,6 +158,10 @@ class AssetPermissionUtilV2: ...@@ -153,6 +158,10 @@ class AssetPermissionUtilV2:
@timeit @timeit
def add_single_assets_node_to_user_tree(self, user_tree): def add_single_assets_node_to_user_tree(self, user_tree):
"""
将单独授权的资产放到树上,如果设置了单独资产到 未分组中,则放到未分组中
如果没有,则查询资产属于的资产组,放到树上
"""
# 添加单独授权资产的节点 # 添加单独授权资产的节点
nodes_single_assets = defaultdict(set) nodes_single_assets = defaultdict(set)
queryset = self.permissions.exclude(assets__isnull=True) \ queryset = self.permissions.exclude(assets__isnull=True) \
...@@ -161,13 +170,26 @@ class AssetPermissionUtilV2: ...@@ -161,13 +170,26 @@ class AssetPermissionUtilV2:
for item in queryset: for item in queryset:
nodes_single_assets[item[1]].add(item[0]) nodes_single_assets[item[1]].add(item[0])
# Todo: 游离资产
nodes_single_assets.pop(None, None) nodes_single_assets.pop(None, None)
for key in tuple(nodes_single_assets.keys()): for key in tuple(nodes_single_assets.keys()):
if user_tree.contains(key): if user_tree.contains(key):
nodes_single_assets.pop(key) nodes_single_assets.pop(key)
# 如果要设置到ungroup中
if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE:
node_key = Node.ungrouped_key
node_value = Node.ungrouped_value
user_tree.create_node(
identifier=node_key, tag=node_value,
parent=user_tree.root,
)
assets = set()
for _assets in nodes_single_assets.values():
assets.update(set(_assets))
user_tree.set_assets(node_key, assets)
return
# 获取单独授权资产,并没有在授权的节点上 # 获取单独授权资产,并没有在授权的节点上
for key, assets in nodes_single_assets.items(): for key, assets in nodes_single_assets.items():
node = self.full_tree.get_node(key, deep=True) node = self.full_tree.get_node(key, deep=True)
...@@ -180,11 +202,17 @@ class AssetPermissionUtilV2: ...@@ -180,11 +202,17 @@ class AssetPermissionUtilV2:
@timeit @timeit
def parse_user_tree_to_full_tree(self, user_tree): def parse_user_tree_to_full_tree(self, user_tree):
"""
经过前面两个动作,用户授权的节点已放到树上,但是树不是完整的,
这里要讲树构造成一个完整的书
"""
# 开始修正user_tree,保证父节点都在树上 # 开始修正user_tree,保证父节点都在树上
root_children = user_tree.children('') root_children = user_tree.children('')
for child in root_children: for child in root_children:
if child.identifier.isdigit(): if child.identifier.isdigit():
continue continue
if child.identifier.startswith('-'):
continue
ancestors = self.full_tree.ancestors( ancestors = self.full_tree.ancestors(
child.identifier, with_self=False, deep=True child.identifier, with_self=False, deep=True
) )
...@@ -194,6 +222,19 @@ class AssetPermissionUtilV2: ...@@ -194,6 +222,19 @@ class AssetPermissionUtilV2:
user_tree.safe_add_ancestors(ancestors) user_tree.safe_add_ancestors(ancestors)
user_tree.move_node(child.identifier, parent_id) user_tree.move_node(child.identifier, parent_id)
@staticmethod
def add_empty_node_if_need(user_tree):
"""
添加空节点,如果根节点没有子节点的话
"""
if not user_tree.children(user_tree.root):
node_key = Node.empty_key
node_value = Node.empty_value
user_tree.create_node(
identifier=node_key, tag=node_value,
parent=user_tree.root,
)
@timeit @timeit
def get_user_tree(self): def get_user_tree(self):
if self._user_tree: if self._user_tree:
...@@ -207,6 +248,7 @@ class AssetPermissionUtilV2: ...@@ -207,6 +248,7 @@ class AssetPermissionUtilV2:
self.add_direct_nodes_to_user_tree(user_tree) self.add_direct_nodes_to_user_tree(user_tree)
self.add_single_assets_node_to_user_tree(user_tree) self.add_single_assets_node_to_user_tree(user_tree)
self.parse_user_tree_to_full_tree(user_tree) self.parse_user_tree_to_full_tree(user_tree)
self.add_empty_node_if_need(user_tree)
self._user_tree = user_tree self._user_tree = user_tree
return user_tree return user_tree
......
...@@ -74,6 +74,14 @@ def construct_remote_apps_tree_root(): ...@@ -74,6 +74,14 @@ def construct_remote_apps_tree_root():
def parse_remote_app_to_tree_node(parent, remote_app): def parse_remote_app_to_tree_node(parent, remote_app):
system_user = remote_app.system_user
user = {
'id': system_user.id,
'name': system_user.name,
'username': system_user.username,
'protocol': system_user.protocol,
'login_mode': system_user.login_mode,
}
tree_node = { tree_node = {
'id': remote_app.id, 'id': remote_app.id,
'name': remote_app.name, 'name': remote_app.name,
...@@ -82,6 +90,6 @@ def parse_remote_app_to_tree_node(parent, remote_app): ...@@ -82,6 +90,6 @@ def parse_remote_app_to_tree_node(parent, remote_app):
'open': False, 'open': False,
'isParent': False, 'isParent': False,
'iconSkin': 'file', 'iconSkin': 'file',
'meta': {'type': 'remote_app'} 'meta': {'type': 'remote_app', 'user': user}
} }
return TreeNode(**tree_node) return TreeNode(**tree_node)
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
<div class="ibox-content mailbox-content" style="padding-top: 0"> <div class="ibox-content mailbox-content" style="padding-top: 0">
<div class="file-manager "> <div class="file-manager ">
<div id="assetTree" class="ztree"> <div id="assetTree" class="ztree">
{% trans 'Loading' %} ...
</div> </div>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment