From 58f0d923707c9b728124bf0e2418ee1d06488257 Mon Sep 17 00:00:00 2001 From: Aarnav Bos Date: Fri, 23 Feb 2018 00:43:11 -0500 Subject: [PATCH] [models] Added automatic removal of old nodes #46 Implements and closes #46 --- README.rst | 16 +++++++++- django_netjsongraph/base/node.py | 23 ++++++++++++++ django_netjsongraph/base/topology.py | 1 + django_netjsongraph/settings.py | 1 + django_netjsongraph/tests/utils.py | 45 ++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6541b2a..4460843 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ django-netjsongraph .. image:: https://badge.fury.io/py/django-netjsongraph.svg :target: http://badge.fury.io/py/django-netjsongraph - + .. image:: https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square :target: https://gitter.im/openwisp/general @@ -276,6 +276,20 @@ Setting this to ``False`` will disable this feature. Path of the visualizer css file. Allows customization of css according to user's preferences. +``NETJSONGRAPH_NODE_EXPIRATION`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ++--------------+--------------------------------+ +| **type**: | ``int`` | ++--------------+--------------------------------+ +| **default**: | ``False`` | ++--------------+--------------------------------+ + +If a node has not been modified since the days specified and if it has no links, +it will be deleted by the ``update_topology`` management command. This depends on +``NETJSONGRAPH_LINK_EXPIRATION`` being enabled. +Replace ``False`` with an integer to enable the feature. + Overriding visualizer templates ------------------------------- diff --git a/django_netjsongraph/base/node.py b/django_netjsongraph/base/node.py index 8174907..dd12833 100644 --- a/django_netjsongraph/base/node.py +++ b/django_netjsongraph/base/node.py @@ -1,12 +1,16 @@ import json from collections import OrderedDict +from datetime import timedelta from django.db import models from django.utils.encoding import python_2_unicode_compatible from django.utils.functional import cached_property +from django.utils.timezone import now from jsonfield import JSONField from rest_framework.utils.encoders import JSONEncoder +from .. import settings +from ..utils import print_info from .base import TimeStampedEditableModel @@ -122,3 +126,22 @@ def count_address(cls, address, topology): address = '{0};'.format(address) return cls.objects.filter(topology=topology, addresses__contains=address).count() + + @classmethod + def delete_expired_nodes(cls): + """ + deletes nodes that have not been connected to the network + for more than ``NETJSONGRAPH__EXPIRATION`` days + """ + NODE_EXPIRATION = settings.NODE_EXPIRATION + LINK_EXPIRATION = settings.LINK_EXPIRATION + if NODE_EXPIRATION not in [False, None] and LINK_EXPIRATION not in [False, None]: + expiration_date = now() - timedelta(days=int(NODE_EXPIRATION)) + expired_nodes = cls.objects.filter(modified__lt=expiration_date, + source_link_set__isnull=True, + target_link_set__isnull=True) + expired_nodes_length = len(expired_nodes) + if expired_nodes_length: + print_info('Deleting {0} expired nodes'.format(expired_nodes_length)) + for node in expired_nodes: + node.delete() diff --git a/django_netjsongraph/base/topology.py b/django_netjsongraph/base/topology.py index d5474bd..eceebff 100644 --- a/django_netjsongraph/base/topology.py +++ b/django_netjsongraph/base/topology.py @@ -323,6 +323,7 @@ def update_all(cls, label=None): with log_failure('update', topology): topology.update() cls().link_model.delete_expired_links() + cls().node_model.delete_expired_nodes() @classmethod def save_snapshot_all(cls, label=None): diff --git a/django_netjsongraph/settings.py b/django_netjsongraph/settings.py index 1997e04..0b85eb3 100644 --- a/django_netjsongraph/settings.py +++ b/django_netjsongraph/settings.py @@ -13,4 +13,5 @@ SIGNALS = getattr(settings, 'NETJSONGRAPH_SIGNALS', None) TIMEOUT = getattr(settings, 'NETJSONGRAPH_TIMEOUT', 8) LINK_EXPIRATION = getattr(settings, 'NETJSONGRAPH_LINK_EXPIRATION', 60) +NODE_EXPIRATION = getattr(settings, 'NETJSONGRAPH_NODE_EXPIRATION', False) VISUALIZER_CSS = getattr(settings, 'NETJSONGRAPH_VISUALIZER_CSS', 'netjsongraph/css/style.css') diff --git a/django_netjsongraph/tests/utils.py b/django_netjsongraph/tests/utils.py index a8001b2..d9f91f6 100644 --- a/django_netjsongraph/tests/utils.py +++ b/django_netjsongraph/tests/utils.py @@ -171,6 +171,51 @@ def test_delete_expired_links(self): self.assertEqual(self.node_model.objects.count(), 2) self.assertEqual(self.link_model.objects.count(), 0) + @responses.activate + def test_delete_expired_nodes(self): + NODE_EXPIRATION = getattr(settings, 'NODE_EXPIRATION') + # Test with the default value(False) + # Should not delete + setattr(settings, 'NODE_EXPIRATION', False) + t = self.topology_model.objects.first() + t.parser = 'netdiff.NetJsonParser' + t.save() + expired_date = now() - timedelta(days=60) + n1 = self.node_model.objects.all()[0] + n2 = self.node_model.objects.all()[1] + self.node_model.objects.filter(pk=n1.pk).update(created=expired_date, + modified=expired_date) + self.node_model.objects.filter(pk=n2.pk).update(created=expired_date, + modified=expired_date) + empty_topology = json.dumps({ + "type": "NetworkGraph", + "protocol": "OLSR", + "version": "0.8", + "metric": "ETX", + "nodes": [], + "links": [] + }) + responses.add(responses.GET, + 'http://127.0.0.1:9090', + body=empty_topology, + content_type='application/json') + self.topology_model.update_all('testnetwork') + self.assertEqual(self.node_model.objects.count(), 2) + + # Test with a custom value + # Should delete + setattr(settings, 'NODE_EXPIRATION', 60) + expired_date = now() - timedelta(days=settings.NODE_EXPIRATION+10) + self.node_model.objects.filter(pk=n1.pk).update(created=expired_date, + modified=expired_date) + self.node_model.objects.filter(pk=n2.pk).update(created=expired_date, + modified=expired_date) + self.topology_model.update_all('testnetwork') + self.assertEqual(self.node_model.objects.count(), 0) + self.assertEqual(self.link_model.objects.count(), 0) + # Set the setting to it's original value + setattr(settings, 'NODE_EXPIRATION', NODE_EXPIRATION) + @responses.activate def test_delete_expired_disabled(self): t = self.topology_model.objects.first()