Changeset - 5193ae7fc0e1
[Not reviewed]
default
0 3 0
Branko Majic (branko) - 10 years ago 2013-11-03 12:03:58
branko@majic.rs
CONNT-20: Implemented output of related items that will get removed as part of cascading on the confirmation page. Includes one custom model and view mixin, and small changes to the delete confirmation template.
3 files changed with 144 insertions and 11 deletions:
0 comments (0 inline, 0 general)
conntrackt/models.py
Show inline comments
 
@@ -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 (<a></a>) 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
 
            (<a></a>) 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('<strong>{0}</strong>: <a href="{1}">{2}</a>', capfirst(obj._meta.verbose_name), obj.get_absolute_url(), str(obj))
 
            except AttributeError:
 
                return format_html('<strong>{0}</strong>: {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
conntrackt/templates/conntrackt/delete_form.html
Show inline comments
 
@@ -15,7 +15,11 @@
 
      <div class="controls controls-row">
 
        {% csrf_token %}
 
        {{ form }}
 
        Are you sure you want to remove this project?
 
        The following entries will be removed:
 
        <ul>
 
          {{ related_items|unordered_list }}
 
        </ul>
 
        Are you sure you want to remove them?
 
      </div>
 
      <hr>
 
      <div class="controls">
conntrackt/views.py
Show inline comments
 
@@ -30,6 +30,7 @@ from django.contrib import messages
 
from django.core.exceptions import ValidationError
 
from django.core.urlresolvers import reverse, reverse_lazy
 
from django.db.models import Q
 
from django.db.models.deletion import Collector
 
from django.http import HttpResponse
 
from django.shortcuts import render_to_response, get_object_or_404
 
from django.views.generic import TemplateView, DetailView, CreateView, UpdateView, DeleteView, View
 
@@ -65,6 +66,43 @@ class RedirectToNextMixin(object):
 
        return self.request.GET.get(self.next_parameter, super(RedirectToNextMixin, self).get_success_url())
 

	
 

	
 
class RelatedItemsMixin(object):
 
    """
 
    View mixin that adds related items of a referenced model object to context
 
    data in form of a nested list, including the reference model object as the
 
    first element of a list.
 

	
 
    The context data will be passed using the "related_items" key.
 

	
 
    This data can be used in the template by passing it through the
 
    unordered_list template tag.
 

	
 
    The reference object is accessed via "object" property of calling view
 
    (i.e. self.object).
 

	
 
    For more details on implementation, see:
 

	
 
    RelatedCollectorMixin.get_dependant_objects_representation method
 
    """
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Adds the related items of a reference model object to context data.
 

	
 
        Returns:
 
          Context data.
 
        """
 

	
 
        # Set the context using the parent class.
 
        context = super(RelatedItemsMixin, self).get_context_data(**kwargs)
 

	
 
        # Add to context the nested list of string representations of related
 
        # items.
 
        context["related_items"] = self.object.get_dependant_objects_representation()
 

	
 
        return context
 

	
 

	
 
class IndexView(MultiplePermissionsRequiredMixin, TemplateView):
 
    """
 
    Custom view used for rendering the index page.
 
@@ -337,7 +375,7 @@ class ProjectUpdateView(RedirectToNextMi
 
        return "Update project %s" % self.object.name
 

	
 

	
 
class ProjectDeleteView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
class ProjectDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting a project.
 
    """
 
@@ -421,7 +459,7 @@ class LocationUpdateView(RedirectToNextM
 
        return "Update location %s" % self.object.name
 

	
 

	
 
class LocationDeleteView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
class LocationDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting a location.
 
    """
 
@@ -538,7 +576,7 @@ class EntityUpdateView(RedirectToNextMix
 
        return "Update entity %s" % self.object.name
 

	
 

	
 
class EntityDeleteView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
class EntityDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting an entity.
 
    """
 
@@ -694,7 +732,7 @@ class InterfaceUpdateView(RedirectToNext
 
        return "Update interface %s" % self.object.name
 

	
 

	
 
class InterfaceDeleteView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
class InterfaceDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting an interface.
 
    """
 
@@ -887,7 +925,7 @@ class CommunicationUpdateView(RedirectTo
 
        return "Update communication %s" % self.object
 

	
 

	
 
class CommunicationDeleteView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
class CommunicationDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting an communication.
 
    """
0 comments (0 inline, 0 general)