diff --git a/conntrackt/models.py b/conntrackt/models.py --- a/conntrackt/models.py +++ b/conntrackt/models.py @@ -20,10 +20,13 @@ # Django imports. +from django.contrib.admin.util import NestedObjects from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse from django.db import models from django.db.models.query_utils import Q +from django.utils.html import format_html +from django.utils.text import capfirst class SearchManager(models.Manager): @@ -47,7 +50,94 @@ class SearchManager(models.Manager): return self.filter(Q(name__icontains=search_term) | Q(description__icontains=search_term)) -class Project(models.Model): +class RelatedCollectorMixin(object): + """ + Implements model mixin for easily obtainning related items of a model + instance. + + The mixin can be used for obtaining all model objects that are directly or + indirectly linked to the calling model object (through foreign key + relationships). + + This mixin is very useful in delete views for warning the user about all of + the related items that will be deleted if the calling item is deleted as + well. + """ + + def get_dependant_objects(self): + """ + Creates a list of model objects that depend (reference), both directly + and indirectly, on calling model object. The calling model object is + included as the first element of the list. This method call can be used + in order to obtain a list of model objects that would get deleted in + case the calling model object gets deleted. + + Returns: + Nested list of model objects that depend (reference) calling model + object. + """ + + collector = NestedObjects(using='default') + + collector.collect([self]) + + return collector.nested() + + def get_dependant_objects_representation(self): + """ + Creates a nested list of object representations that depend (reference), + both directly and indirectly, calling model object. This method call can + be used in order to obtain a list of string representations of model + objects that would get deleted in case the calling model object gets + deleted. + + The resulting nested list can be shown to the user for + warning/notification purposes using the unordered_list template tag. + + Each non-list element will be a string of format: + + MODEL_NAME: OBJECT_REPRESENTATION + + If object has a callable get_absolute_url method, the object + representation will be surrouned by HTML anchor tag () where + target (href) is set to the value of get_absolute_url() method call. + + Returns: + Nested list of representations of model objects that depend + (reference) calling model object. + """ + + collector = NestedObjects(using='default') + + collector.collect([self]) + + def formatter_callback(obj): + """ + Creates model object representation in format: + + MODEL_NAME: OBJECT_REPRESENTATION + + If passed object has a callable get_absolute_url method, the + instance representation will be surrouned by an HTML anchor + () where target is set to value of the get_absolute_url() + method call. + + Arguments: + obj - Model object whose representation should be returned. + + Returns: + String represenation of passed model object. + """ + + try: + return format_html('{0}: {2}', capfirst(obj._meta.verbose_name), obj.get_absolute_url(), str(obj)) + except AttributeError: + return format_html('{0}: {1}', capfirst(obj._meta.verbose_name), str(obj)) + + return collector.nested(formatter_callback) + + +class Project(RelatedCollectorMixin, models.Model): """ Implements a model with information about a project. A project has some basic settings, and mainly serves the purpose of grouping entities for @@ -62,6 +152,7 @@ class Project(models.Model): name = models.CharField(max_length=100, unique=True) description = models.TextField(blank=True) objects = SearchManager() + deletion_collect_models = ["Entity", "Interface"] class Meta: permissions = (("view", "Can view information"),) @@ -82,7 +173,7 @@ class Project(models.Model): return reverse("project", kwargs={'pk': self.pk}) -class Location(models.Model): +class Location(RelatedCollectorMixin, models.Model): """ Implements a model with information about location. Locations can further be assigned to entities, letting the user group different servers and equipment @@ -118,7 +209,7 @@ class Location(models.Model): return self.name -class Entity(models.Model): +class Entity(RelatedCollectorMixin, models.Model): """ Models an entity in a project. An entity can be a server, router, or any other piece of networking equipment that has its own IP address. @@ -215,7 +306,7 @@ class Entity(models.Model): raise ValidationError("The entity cannot be moved to different project as long as it has valid communications with entities in current project.") -class Interface(models.Model): +class Interface(RelatedCollectorMixin, models.Model): """ Models a representation of an interface on an entity. It can be used for representing the subnets as well. @@ -261,7 +352,7 @@ class Interface(models.Model): return '%s (%s/%s)' % (self.entity.name, self.address, self.netmask) -class Communication(models.Model): +class Communication(RelatedCollectorMixin, models.Model): """ Models a representation of allowed network communication. This lets the user display the possible network connections that should be allowed. Information