From b95f8a7d6b55e74b29cf7a345f42c3d771f6bcd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=80=81=E5=B9=BF?= <ibuler@qq.com>
Date: Mon, 17 Dec 2018 11:49:57 +0800
Subject: [PATCH] =?UTF-8?q?[Update]=20=E4=BF=AE=E5=A4=8D=E8=8E=B7=E5=8F=96?=
 =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=95=B0=E9=87=8F=E6=AF=94=E8=BE=83=E6=85=A2?=
 =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98=20(#2184)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 apps/assets/api/node.py                       |  3 +-
 apps/assets/models/node.py                    | 79 ++++++++++++-------
 apps/assets/serializers/node.py               | 18 ++---
 apps/assets/signals_handler.py                | 20 ++++-
 .../templates/assets/_asset_list_modal.html   |  1 +
 apps/assets/templates/assets/asset_list.html  |  7 ++
 6 files changed, 83 insertions(+), 45 deletions(-)

diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py
index 831c85e8a..73313f222 100644
--- a/apps/assets/api/node.py
+++ b/apps/assets/api/node.py
@@ -20,11 +20,10 @@ from rest_framework.response import Response
 from rest_framework_bulk import BulkModelViewSet
 from django.utils.translation import ugettext_lazy as _
 from django.shortcuts import get_object_or_404
-from django.db.models import Count
 
 from common.utils import get_logger, get_object_or_none
 from ..hands import IsOrgAdmin
-from ..models import Node
+from ..models import Node, Asset
 from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
 from .. import serializers
 
diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py
index af4ffd839..47a835861 100644
--- a/apps/assets/models/node.py
+++ b/apps/assets/models/node.py
@@ -22,7 +22,9 @@ class Node(OrgModelMixin):
     date_create = models.DateTimeField(auto_now_add=True)
 
     is_node = True
-    _full_value_cache_key_prefix = '_NODE_VALUE_{}'
+    _assets_amount = None
+    _full_value_cache_key = '_NODE_VALUE_{}'
+    _assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
 
     class Meta:
         verbose_name = _("Node")
@@ -49,30 +51,56 @@ class Node(OrgModelMixin):
     def name(self):
         return self.value
 
+    @property
+    def assets_amount(self):
+        """
+        获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
+        :return:
+        """
+        if self._assets_amount is not None:
+            return self._assets_amount
+        cache_key = self._assets_amount_cache_key.format(self.key)
+        cached = cache.get(cache_key)
+        if cached is not None:
+            return cached
+        assets_amount = self.get_all_assets().count()
+        cache.set(cache_key, assets_amount, 3600)
+        return assets_amount
+
+    @assets_amount.setter
+    def assets_amount(self, value):
+        self._assets_amount = value
+
+    def expire_assets_amount(self):
+        ancestor_keys = self.get_ancestor_keys(with_self=True)
+        cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
+        cache.delete_many(cache_keys)
+
+    @classmethod
+    def expire_nodes_assets_amount(cls, nodes=None):
+        if nodes:
+            for node in nodes:
+                node.expire_assets_amount()
+            return
+        key = cls._assets_amount_cache_key.format('*')
+        cache.delete_pattern(key)
+
     @property
     def full_value(self):
-        key = self._full_value_cache_key_prefix.format(self.key)
+        key = self._full_value_cache_key.format(self.key)
         cached = cache.get(key)
         if cached:
             return cached
-        value = self.get_full_value()
-        self.cache_full_value(value)
-        return value
-
-    def get_full_value(self):
-        # ancestor = [a.value for a in self.get_ancestor(with_self=True)]
         if self.is_root():
             return self.value
         parent_full_value = self.parent.full_value
         value = parent_full_value + ' / ' + self.value
+        key = self._full_value_cache_key.format(self.key)
+        cache.set(key, value, 3600)
         return value
 
-    def cache_full_value(self, value):
-        key = self._full_value_cache_key_prefix.format(self.key)
-        cache.set(key, value, 3600)
-
     def expire_full_value(self):
-        key = self._full_value_cache_key_prefix.format(self.key)
+        key = self._full_value_cache_key.format(self.key)
         cache.delete_pattern(key+'*')
 
     @property
@@ -182,17 +210,18 @@ class Node(OrgModelMixin):
                 child.save()
             self.save()
 
-    def get_ancestor(self, with_self=False):
-        if self.is_root():
-            root = self.__class__.root()
-            return [root]
-        _key = self.key.split(':')
+    def get_ancestor_keys(self, with_self=False):
+        parent_keys = []
+        key_list = self.key.split(":")
         if not with_self:
-            _key.pop()
-        ancestor_keys = []
-        for i in range(len(_key)):
-            ancestor_keys.append(':'.join(_key))
-            _key.pop()
+            key_list.pop()
+        for i in range(len(key_list)):
+            parent_keys.append(":".join(key_list))
+            key_list.pop()
+        return parent_keys
+
+    def get_ancestor(self, with_self=False):
+        ancestor_keys = self.get_ancestor_keys(with_self=with_self)
         ancestor = self.__class__.objects.filter(
             key__in=ancestor_keys
         ).order_by('key')
@@ -227,10 +256,6 @@ class Node(OrgModelMixin):
         defaults = {'value': 'Default'}
         return cls.objects.get_or_create(defaults=defaults, key='1')
 
-    @classmethod
-    def get_tree_name_ref(cls):
-        pass
-
     @classmethod
     def generate_fake(cls, count=100):
         import random
diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py
index f1be42d06..f44ff44d6 100644
--- a/apps/assets/serializers/node.py
+++ b/apps/assets/serializers/node.py
@@ -43,7 +43,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
 
 
 class NodeSerializer(serializers.ModelSerializer):
-    assets_amount = serializers.SerializerMethodField()
+    assets_amount = serializers.IntegerField()
     tree_id = serializers.SerializerMethodField()
     tree_parent = serializers.SerializerMethodField()
 
@@ -53,6 +53,10 @@ class NodeSerializer(serializers.ModelSerializer):
             'id', 'key', 'value', 'assets_amount',
             'is_node', 'org_id', 'tree_id', 'tree_parent',
         ]
+        read_only_fields = [
+            'id', 'key', 'assets_amount', 'is_node',
+            'org_id',
+        ]
         list_serializer_class = BulkListSerializer
 
     def validate(self, data):
@@ -66,12 +70,6 @@ class NodeSerializer(serializers.ModelSerializer):
             )
         return data
 
-    @staticmethod
-    def get_assets_amount(obj):
-        if hasattr(obj, 'assets_amount'):
-            return obj.assets_amount
-        return obj.get_all_assets().count()
-
     @staticmethod
     def get_tree_id(obj):
         return obj.key
@@ -80,12 +78,6 @@ class NodeSerializer(serializers.ModelSerializer):
     def get_tree_parent(obj):
         return obj.parent_key
 
-    def get_fields(self):
-        fields = super().get_fields()
-        field = fields["key"]
-        field.required = False
-        return fields
-
 
 class NodeAssetsSerializer(serializers.ModelSerializer):
     assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
diff --git a/apps/assets/signals_handler.py b/apps/assets/signals_handler.py
index 9028f52c3..08ee6e670 100644
--- a/apps/assets/signals_handler.py
+++ b/apps/assets/signals_handler.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 #
 from collections import defaultdict
-from django.db.models.signals import post_save, m2m_changed
+from django.db.models.signals import post_save, m2m_changed, post_delete
 from django.dispatch import receiver
 
 from common.utils import get_logger
@@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
         update_asset_hardware_info_on_created(instance)
         test_asset_conn_on_created(instance)
 
+        # 过期节点资产数量
+        nodes = instance.nodes.all()
+        Node.expire_nodes_assets_amount(nodes)
+
+
+@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
+def on_asset_delete(sender, instance=None, **kwargs):
+    # 过期节点资产数量
+    nodes = instance.nodes.all()
+    Node.expire_nodes_assets_amount(nodes)
+
 
 @receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
 def on_system_user_update(sender, instance=None, created=True, **kwargs):
@@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
 
 @receiver(m2m_changed, sender=Asset.nodes.through)
 def on_asset_node_changed(sender, instance=None, **kwargs):
+    logger.debug("Asset node change signal received")
     if isinstance(instance, Asset):
         if kwargs['action'] == 'post_add':
-            logger.debug("Asset node change signal received")
             nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
+            Node.expire_nodes_assets_amount(nodes)
             system_users_assets = defaultdict(set)
             system_users = SystemUser.objects.filter(nodes__in=nodes)
             # 清理节点缓存
@@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
 @receiver(m2m_changed, sender=Asset.nodes.through)
 def on_node_assets_changed(sender, instance=None, **kwargs):
     if isinstance(instance, Node):
+        logger.debug("Node assets change signal received")
+        # 当节点和资产关系发生改变时,过期资产数量缓存
+        instance.expire_assets_amount()
         assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
         if kwargs['action'] == 'post_add':
-            logger.debug("Node assets change signal received")
             # 重新关联系统用户和资产的关系
             system_users = SystemUser.objects.filter(nodes=instance)
             for system_user in system_users:
diff --git a/apps/assets/templates/assets/_asset_list_modal.html b/apps/assets/templates/assets/_asset_list_modal.html
index ea8d59e49..fe50ac2b3 100644
--- a/apps/assets/templates/assets/_asset_list_modal.html
+++ b/apps/assets/templates/assets/_asset_list_modal.html
@@ -116,6 +116,7 @@ function initTree2() {
 
 
 $(document).ready(function(){
+}).on('show.bs.modal', function () {
     initTable2();
     initTree2();
 })
diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html
index 7e093688f..bd3a776b3 100644
--- a/apps/assets/templates/assets/asset_list.html
+++ b/apps/assets/templates/assets/asset_list.html
@@ -305,6 +305,9 @@ function onSelected(event, treeNode) {
 }
 
 function selectQueryNode() {
+    // TODO: 是否应该添加
+    // 暂时忽略之前选中的内容
+    return
     var query_node_id = $.getUrlParam("node");
     var cookie_node_id = getCookie('node_selected');
     var node;
@@ -355,6 +358,9 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
 }
 
 function initTree() {
+    if (zTree) {
+        return
+    }
     var setting = {
         view: {
             dblClickExpand: false,
@@ -387,6 +393,7 @@ function initTree() {
     };
 
     var zNodes = [];
+    console.log("Get assets")
     $.get("{% url 'api-assets:node-list' %}", function(data, status){
         $.each(data, function (index, value) {
             value["node_id"] = value["id"];