Changeset - 744593ed423d
[Not reviewed]
default
0 6 3
Branko Majic (branko) - 11 years ago 2013-07-20 12:38:54
branko@majic.rs
CONNT-6: Implemented editing of communications from within the application. Cleaned-up the entity details template to have better looks.
9 files changed with 387 insertions and 28 deletions:
0 comments (0 inline, 0 general)
conntrackt/forms.py
Show inline comments
 
@@ -3,7 +3,7 @@ from django.forms import ModelForm
 
from django.forms.models import inlineformset_factory
 

	
 
# Application imports.
 
from .models import Entity, Interface
 
from .models import Entity, Interface, Communication
 

	
 

	
 
class EntityForm(ModelForm):
 
@@ -56,3 +56,28 @@ class InterfaceForm(ModelForm):
 
        self.fields["description"].widget.attrs["placeholder"] = "Interface description"
 
        self.fields["address"].widget.attrs["placeholder"] = "IP address of interface"
 
        self.fields["netmask"].widget.attrs["placeholder"] = "IP address netmask"
 

	
 

	
 
class CommunicationForm(ModelForm):
 
    """
 
    Implements a custom model form for communications with some styling changes.
 
    """
 

	
 
    class Meta:
 
        model = Communication
 

	
 
    def __init__(self, *args, **kwargs):
 
        """
 
        Initialises the form instance. Sets-up some bootstrap CSS classes for
 
        widgets.
 
        """
 

	
 
        super(CommunicationForm, self).__init__(*args, **kwargs)
 

	
 
        # Update the widgets to be wider.
 
        for field_name, field in self.fields.iteritems():
 
            field.widget.attrs["class"] = "span6"
 

	
 
        # Set-up some placeholders.
 
        self.fields["port"].widget.attrs["placeholder"] = "Port used for communication"
 
        self.fields["description"].widget.attrs["placeholder"] = "Communication description"
conntrackt/models.py
Show inline comments
 
@@ -301,3 +301,31 @@ class Communication(models.Model):
 
        """
 

	
 
        return "Edit"
 

	
 
    def source_representation(self):
 
        """
 
        Produces string representation of communication that includes only the
 
        source interface information.
 

	
 
        The method is useful where the destination context is well known.
 

	
 
        Returns:
 
            Communication representation that includes only information about
 
            the source interface.
 
        """
 

	
 
        return "%s - %s: %d" % (self.source, self.protocol, self.port)
 

	
 
    def destination_representation(self):
 
        """
 
        Produces string representation of communication that includes only the
 
        destination interface information.
 

	
 
        The method is useful where the source context is well known.
 

	
 
        Returns:
 
            Communication representation that includes only information about
 
            the destination interface.
 
        """
 

	
 
        return "%s - %s: %d" % (self.destination, self.protocol, self.port)
conntrackt/templates/conntrackt/communication_confirm_delete.html
Show inline comments
 
new file 100644
 
{% extends "conntrackt/base.html" %}
 

	
 
{# For html_link #}
 
{% load conntrackt_tags %}
 
{# For Bootstrapped forms #}
 
{% load crispy_forms_tags %}
 

	
 
{% block content %}
 
<div class="row">
 
  <h1 class="span12">Remove communication {{communication}}</h1>
 
</div>
 
<div class="row">
 
  <div class="span12">
 
    <form action="" method="post">
 
      <div class="controls controls-row">
 
        {% csrf_token %}
 
        {{ form }}
 
        Are you sure you want to remove this communication?
 
      </div>
 
      <hr>
 
      <div class="controls">
 
        <button type="submit" class="btn btn-primary">Remove</button>
 
      </div>
 
    </form>
 
  </div>
 
</div>
 
{% endblock content %}
conntrackt/templates/conntrackt/communication_create_form.html
Show inline comments
 
new file 100644
 
{% extends "conntrackt/base.html" %}
 

	
 
{# For html_link #}
 
{% load conntrackt_tags %}
 
{# For Bootstrapped forms #}
 
{% load crispy_forms_tags %}
 

	
 
{% block content %}
 
<div class="row">
 
  <h1 class="span12">Add new communication</h1>
 
</div>
 

	
 
<div class="row">
 
  <div class="span6">
 
    <form action="" method="post">
 
      <div class="controls controls-row">
 
      {% csrf_token %}
 
      {{ form | crispy }}
 
      {{ interface_form | crispy }}
 
      </div>
 
      <div class="controls">
 
        <button type="submit" class="btn btn-primary">Add</button>
 
      </div>
 
    </form>
 
  </div>
 
</div>
 
{% endblock content %}
conntrackt/templates/conntrackt/communication_update_form.html
Show inline comments
 
new file 100644
 
{% extends "conntrackt/base.html" %}
 

	
 
{# For html_link #}
 
{% load conntrackt_tags %}
 
{# For Bootstrapped forms #}
 
{% load crispy_forms_tags %}
 

	
 
{% block content %}
 
<div class="row">
 
  <h1 class="span12">Edit communication {{interface.name}}</h1>
 
</div>
 
<div class="row">
 
  <div class="span6">
 
    <form action="" method="post">
 
      <div class="controls controls-row">
 
      {% csrf_token %}
 
      {{ form | crispy }}
 
      </div>
 
      <div class="controls">
 
        <button type="submit" class="btn btn-primary">Update</button>
 
      </div>
 
    </form>
 
  </div>
 
</div>
 
{% endblock content %}
conntrackt/templates/conntrackt/entity_detail.html
Show inline comments
 
@@ -24,9 +24,6 @@
 
    {% html_link "Edit" "entity_update" entity.id class="btn btn-primary" %}
 
    {% html_link "Remove" "entity_delete" entity.id class="btn btn-primary" %}
 
    {% html_link "Get Iptables" 'entity_iptables' entity.id class="btn btn-primary" %}
 
    {% with entity_id=entity.id|slugify %}
 
    {% html_link "Add interface" "interface_create" class="btn btn-primary" get="entity="|add:entity_id %}
 
    {% endwith %}
 
  </div>
 
</div>
 

	
 
@@ -35,12 +32,10 @@
 
<div class="row">
 

	
 
  <div class="span6">
 
    <h2>General information</h2>
 
    <div class="well">
 
      <table class="table table-striped">
 
        <tr>
 
          <th colspan="2">General information</th>
 
        </tr>
 
        <tr>
 
          <th>Project</th><td style="width:99%">{% html_link project.name 'project'  project.id  %}</td>
 
        </tr>
 
        <tr>
 
@@ -51,56 +46,86 @@
 
  </div>
 

	
 
  <div class="span6">
 
    <h2>Interfaces</h2>
 
    <div class="well">
 
      {% if interfaces %}
 
      <table class="table table-striped">
 
        <tr>
 
          <th colspan="3">Interfaces</th>
 
        </tr>
 
        {% for interface in interfaces %}
 
          <tr>
 
            <td style="width:99%">{{interface.name}} ({{interface.address}}/{{interface.netmask}})</td>
 
            <td>{% html_link '<i class="icon-edit"></i>' 'interface_update' interface.id class="btn btn-link"  %}</td>
 
            <td>{% html_link '<i class="icon-remove"></i>' 'interface_delete' interface.id class="btn btn-link"  %}</td>
 
          </tr>
 
        <tr>
 
          <td style="width:99%"><p>{{interface.name}} ({{interface.address}}/{{interface.netmask}})</p></td>
 
          <td>{% html_link '<i class="icon-edit"></i>' 'interface_update' interface.id class="btn btn-link"  %}</td>
 
          <td>{% html_link '<i class="icon-remove"></i>' 'interface_delete' interface.id class="btn btn-link"  %}</td>
 
        </tr>
 
        {% endfor %}
 
      </table>
 
      {% else %}
 
      <p>No interfaces are defined for this entity.</p>
 
      {% endif %}
 
      <div>
 
        {% with entity_id=entity.id|slugify %}
 
        {% html_link "Add interface" "interface_create" class="btn btn-primary btn-mini" get="entity="|add:entity_id %}
 
        {% endwith %}
 
      </div>
 
    </div>
 
  </div>
 
</div>
 

	
 
<div class="row">
 
  <div class="span6">
 
    <h2>Incoming communications</h2>
 
    <div class="well">
 
      {% if incoming_communications %}
 
      <table class="table table-striped">
 
        {% for comm in incoming_communications %}
 
        <tr>
 
          <th>Incoming communications</th>
 
          <td style="width:99%">{% html_link comm.source_representation "entity" comm.source.entity.id class="btn btn-link" %}</td>
 
          <td>{% html_link '<i class="icon-edit"></i>' 'communication_update' comm.id class="btn btn-link"  %}</td>
 
          <td>{% html_link '<i class="icon-remove"></i>' 'communication_delete' comm.id class="btn btn-link"  %}</td>
 
        </tr>
 
        {% for comm in incoming_communications %}
 
          <tr><td>{{comm.source}} - {{comm.protocol}}: {{comm.port}}</td></tr>
 
        {% endfor %}
 
      </table>
 
      {% else %}
 
      <p>No incoming communications towards this entity.</p>
 
      {% endif %}
 
      <div>
 
        {% with entity_id=entity.id|slugify %}
 
        {% html_link "Add communication" "communication_create" class="btn btn-primary btn-mini" get="to_entity="|add:entity_id %}
 
        {% endwith %}
 
      </div>
 
    </div>
 
  </div>
 

	
 
  <div class="span6">
 
    <h2>Outgoing communications</h2>
 
    <div class="well">
 
      {% if outgoing_communications %}
 
      <table class="table table-striped">
 
        <tr>
 
          <th>Outgoing communications</th>
 
        </tr>
 
        {% for comm in outgoing_communications %}
 
          <tr><td>{{comm.destination}} - {{comm.protocol}}: {{comm.port}}</td></tr>
 
        <tr>
 
          <td style="width:99%">{% html_link comm.destination_representation "entity" comm.destination.entity.id class="btn btn-link" %}</td>
 
          <td>{% html_link '<i class="icon-edit"></i>' 'communication_update' comm.id class="btn btn-link"  %}</td>
 
          <td>{% html_link '<i class="icon-remove"></i>' 'communication_delete' comm.id class="btn btn-link"  %}</td>
 
        </tr>
 
        {% endfor %}
 
        <tr>
 
        </tr>
 
      </table>
 
      {% else %}
 
      <p>No outgoing communications from this entity.</p>
 
      {% endif %}
 
      <div>
 
        {% with entity_id=entity.id|slugify %}
 
        {% html_link "Add communication" "communication_create" class="btn btn-primary btn-mini" get="from_entity="|add:entity_id %}
 
        {% endwith %}
 
      </div>
 
    </div>
 
  </div>
 

	
 
  <div class="span12">
 
    <div>
 
      <p><strong>Iptables rules</strong></p>
 
    <h2>Iptables rules</h2>
 
    <div class="well">
 
      <pre>{{ entity_iptables }}</pre>
 
      <div>
 
        {% html_link "Download" 'entity_iptables' entity.id class="btn btn-primary" %}
 
      </div>
 
    </div>
 
  </div>
 
  
conntrackt/templates/conntrackt/project_detail.html
Show inline comments
 
@@ -23,6 +23,7 @@
 
    {% html_link "Remove" "project_delete" project.id class="btn btn-primary" %}
 
    {% with project_id=project.id|slugify %}
 
    {% html_link "Add entity" "entity_create" class="btn btn-primary" get="project="|add:project_id %}
 
    {% html_link "Add communication" "communication_create" class="btn btn-primary" get="project="|add:project_id %}
 
    {% endwith %}
 
  </div>
 
</div>
conntrackt/urls.py
Show inline comments
 
@@ -8,6 +8,7 @@ from .views import ProjectView, ProjectC
 
from .views import LocationCreateView, LocationUpdateView, LocationDeleteView
 
from .views import EntityCreateView, EntityUpdateView, EntityDeleteView
 
from .views import InterfaceCreateView, InterfaceUpdateView, InterfaceDeleteView
 
from .views import CommunicationCreateView, CommunicationUpdateView, CommunicationDeleteView
 

	
 

	
 
urlpatterns = patterns(
 
@@ -50,6 +51,13 @@ urlpatterns = patterns(
 
    # View for deleting an interface.
 
    url(r'^interface/(?P<pk>\d+)/remove/$', InterfaceDeleteView.as_view(), name="interface_delete"),
 

	
 
    # View for creating a new communucation.
 
    url(r'^communication/add/$', CommunicationCreateView.as_view(), name="communication_create"),
 
    # View for updating an existing communication.
 
    url(r'^communication/(?P<pk>\d+)/edit/$', CommunicationUpdateView.as_view(), name="communication_update"),
 
    # View for deleting a communication.
 
    url(r'^communication/(?P<pk>\d+)/remove/$', CommunicationDeleteView.as_view(), name="communication_delete"),
 

	
 
    # View for rendering iptables rules for a specific entity.
 
    url(r'^entity/(?P<pk>\d+)/iptables/$', entity_iptables, name="entity_iptables"),
 
    # View for rendering zip file with iptables rules for all entities in a project.
conntrackt/views.py
Show inline comments
 
@@ -14,8 +14,8 @@ from django.views.generic import Templat
 
from braces.views import MultiplePermissionsRequiredMixin
 

	
 
# Application imports.
 
from .forms import EntityForm, InterfaceForm
 
from .models import Project, Entity, Location, Interface
 
from .forms import EntityForm, InterfaceForm, CommunicationForm
 
from .models import Project, Entity, Location, Interface, Communication
 
from .utils import generate_entity_iptables
 

	
 

	
 
@@ -680,3 +680,196 @@ class InterfaceDeleteView(MultiplePermis
 
        self.success_url = reverse("entity", args=(self.get_object().entity.id,))
 

	
 
        return super(InterfaceDeleteView, self).delete(*args, **kwargs)
 

	
 

	
 
class CommunicationCreateView(MultiplePermissionsRequiredMixin, CreateView):
 
    """
 
    View for creating a new communication.
 
    """
 

	
 
    model = Communication
 
    form_class = CommunicationForm
 
    template_name_suffix = "_create_form"
 

	
 
    # Required permissions
 
    permissions = {
 
        "all": ("conntrackt.add_communication",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_form(self, form_class):
 
        """
 
        Returns an instance of form that can be used by the view.
 

	
 
        The method will limit the source and destination interface selection to
 
        interfaces belonging to the same project as provided entity ID (if any
 
        was provided).
 
        """
 

	
 
        form = super(CommunicationCreateView, self).get_form(form_class)
 

	
 
        # Limit the interface selection based on provided source entity,
 
        # destination entity, or project.
 
        entity_id = self.request.GET.get("from_entity", None)
 
        entity_id = self.request.GET.get("to_entity", None)
 
        project_id = self.request.GET.get("project", None)
 

	
 
        if project_id:
 
            form.fields["source"].queryset = Interface.objects.filter(entity__project=project_id)
 
            form.fields["destination"].queryset = Interface.objects.filter(entity__project=project_id)
 
        elif entity_id:
 
            entity = Entity.objects.get(pk=1)
 
            form.fields["source"].queryset = Interface.objects.filter(entity__project=entity.project)
 
            form.fields["destination"].queryset = Interface.objects.filter(entity__project=entity.project)
 

	
 
        return form
 

	
 
    def get_initial(self):
 
        """
 
        Returns initial values that should be pre-selected (if they were
 
        specified through a GET parameter).
 
        """
 

	
 
        initial = super(CommunicationCreateView, self).get_initial()
 

	
 
        # If source or destination entity were specified in request, fetch the
 
        # first interface from them and use it as initial source and destination.
 
        from_entity = self.request.GET.get("from_entity", None)
 
        to_entity = self.request.GET.get("to_entity", None)
 

	
 
        if from_entity:
 
            try:
 
                interface = Interface.objects.filter(entity=from_entity)[0]
 
                initial["source"] = interface.id
 
            except IndexError:
 
                pass
 

	
 
        if to_entity:
 
            try:
 
                interface = Interface.objects.filter(entity=to_entity)[0]
 
                initial["destination"] = interface.id
 
            except IndexError:
 
                pass
 

	
 
        return initial
 

	
 
    def get_success_url(self):
 
        """
 
        Returns the URL to which the user should be redirected after a
 
        communication has been created.
 

	
 
        The URL will be set to entity details page of an entity that was
 
        provided as part of the from/to GET request (in that order), or as a
 
        fallback it'll direct the user to source interface's entity details
 
        page.
 
        """
 

	
 
        entity_id = self.request.GET.get("from_entity", None)
 
        entity_id = self.request.GET.get("to_entity", None)
 

	
 
        if entity_id is None:
 
            entity_id = self.object.source.entity.pk
 

	
 
        return reverse("entity", args=(entity_id,))
 

	
 

	
 
class CommunicationUpdateView(MultiplePermissionsRequiredMixin, UpdateView):
 
    """
 
    View for updating an existing communication.
 
    """
 

	
 
    model = Communication
 
    form_class = CommunicationForm
 
    template_name_suffix = "_update_form"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.change_communication",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_form(self, form_class):
 
        """
 
        Returns an instance of form that can be used by the view.
 

	
 
        The method will limit the source and destination interfaces that can be
 
        selected for the communication. Both will be limited to interfaces
 
        coming from entities that belong to the same project as current
 
        communication's source interface.
 
        """
 

	
 
        form = super(CommunicationUpdateView, self).get_form(form_class)
 

	
 
        project = self.object.source.entity.project
 

	
 
        form.fields["source"].queryset = Interface.objects.filter(entity__project=project)
 
        form.fields["destination"].queryset = Interface.objects.filter(entity__project=project)
 

	
 
        return form
 

	
 
    def get_success_url(self):
 
        """
 
        Returns the URL to which the user should be redirected after a
 
        communication has been created.
 

	
 
        The URL will be set to entity details page of an entity that was
 
        provided as part of the from/to GET request (in that order), or as a
 
        fallback it'll direct the user to source interface's entity details
 
        page.
 
        """
 

	
 
        entity_id = self.request.GET.get("from_entity", None)
 
        entity_id = self.request.GET.get("to_entity", None)
 

	
 
        if entity_id is None:
 
            entity_id = self.object.source.entity.pk
 

	
 
        return reverse("entity", args=(entity_id,))
 

	
 

	
 
class CommunicationDeleteView(MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting an communication.
 
    """
 

	
 
    model = Communication
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.delete_communication",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def post(self, *args, **kwargs):
 
        """
 
        Add a success message that will be displayed to the user to confirm the
 
        communication deletion.
 
        """
 

	
 
        messages.success(self.request, "Communication %s has been removed." % self.get_object(), extra_tags="alert alert-success")
 

	
 
        return super(CommunicationDeleteView, self).post(*args, **kwargs)
 

	
 
    def delete(self, *args, **kwargs):
 
        """
 
        Deletes the object. This method is overridden in order to obtain the
 
        entity ID for success URL.
 

	
 
        @TODO: Fix this once Django 1.6 comes out with fix from ticket 19044.
 
        """
 

	
 
        entity_id = self.request.GET.get("from_entity", None)
 
        entity_id = self.request.GET.get("to_entity", None)
 

	
 
        if entity_id is None:
 
            entity_id = self.get_object().source.entity.pk
 

	
 
        self.success_url = reverse("entity", args=(entity_id,))
 

	
 
        return super(CommunicationDeleteView, self).delete(*args, **kwargs)
0 comments (0 inline, 0 general)