Changeset - 02c5e6c3ba98
[Not reviewed]
default
0 8 3
Branko Majic (branko) - 11 years ago 2013-03-16 17:50:46
branko@majic.rs
Implemented a much more flexible html_link tag that can accept zero or more view arguments. Major styling changes in the templates to use more compact/appealing looks. The links are now laid-out in tables in order to have better alignment of iptables rule download links. Project view will now display entities grouped by locations. Fixed-up some content dispositions for suggesting better names to browser. Implemented view for fetching a ZIP archive that contains iptables rules for all involved entities (supports fetching for project, or for project location). Added widget templaes for project and location that render list of entities in a frame. Added missing documentation for Python code.
11 files changed with 224 insertions and 43 deletions:
0 comments (0 inline, 0 general)
conntrackt/models.py
Show inline comments
 
# Basic classes needed for creating models.
 
# Django-specific imports
 
from django.core.exceptions import ValidationError
 
from django.db import models
 

	
 
# 
 
from django.core.exceptions import ValidationError
 

	
 
# Create your models here.
 
class Project(models.Model):
 
    """
conntrackt/templates/conntrackt/entity_detail.html
Show inline comments
 
@@ -7,8 +7,8 @@
 
{% if entity %}
 
<h1>{{entity.name}}</h1>
 
<dl>
 
<dt>Project</dt><dd>{% html_link 'project' project.name project.id %}</dd>
 
<dt>Location</dt><dd>{% html_link 'location' location.name location.id %}</dd>
 
<dt>Project</dt><dd>{% html_link project.name 'project'  project.id  %}</dd>
 
<dt>Location</dt><dd>{% html_link location.name 'location' location.id %}</dd>
 
<dt>Incoming communications</dt><dd><ul>{% for interface in entity.interface_set.all %}
 
{% for communication in interface.destination_set.all %}
 
<li>{{communication.destination}} - {{communication.protocol}}: {{communication.port}}</li>
 
@@ -17,7 +17,7 @@
 
{% for communication in interface.source_set.all %}
 
<li>{{communication.destination}} - {{communication.protocol}}: {{communication.port}}</li>
 
{% endfor %}{% endfor %}</ul></dd>
 
<dt>iptables rules</dt><dd><pre>{% include "conntrackt/entity_iptables.html" with entity=entity %}</pre>{% html_link 'entity_iptables' 'Download' entity.id "btn btn-primary" %}</dd>
 
<dt>iptables rules</dt><dd><pre>{% include "conntrackt/entity_iptables.html" with entity=entity %}</pre>{% html_link 'Download' 'entity_iptables' entity.id class="btn btn-primary" %}</dd>
 
</dl>
 
{% endif %}
 
{% endwith %}
conntrackt/templates/conntrackt/html_link.html
Show inline comments
 
new file 100644
 
{% load url from future %}
 
<a href="{{url}}"{% if title %} title="{{title}}"{% endif %}{% if id %} id="{{id}}"{% endif %}{%if class %} class="{{class}}"{% endif %}>{{text}}</a>
conntrackt/templates/conntrackt/index.html
Show inline comments
 
@@ -5,12 +5,16 @@
 
{% block content %}
 
<h1>Welcome to Conntrackt</h1>
 
<div>Below you may find the list of projects that are available to you. Clicking on the project will take you to the summary page for that particular project.</div>
 
<hr>
 
{% if projects %}
 
<div class="row">
 
  {% for project in projects %}
 
<ul class="unstyled">
 
  <li>{% html_link 'project' project.name project.id "btn btn-link" %}</li>
 
</ul>
 
  <div class="span4">
 
    <div class="well">{% include "conntrackt/project_widget.html" %}</div>
 
  </div>
 
  {% endfor %}
 
</div>
 
{% endif %}
 

	
 
{% endblock %}
 

	
conntrackt/templates/conntrackt/location_widget.html
Show inline comments
 
new file 100644
 
{% load conntrackt %}
 
  <table class="table table-striped">
 
    <tr><td><strong>{{location.name}}</strong></td><td>{% html_link '<i class="icon-book"></i>' 'project_location_iptables' project.id location.id class="btn btn-link" %}</td></tr>
 
{% for entity in entities %}
 
    <tr><td>{% html_link entity.name 'entity' entity.id class="btn btn-link" %}</td><td>{% html_link '<i class="icon-list"></i>' 'entity_iptables' entity.id class="btn btn-link" %}</td></tr>
 
{% endfor %}
 
  </table>
 

	
conntrackt/templates/conntrackt/project_detail.html
Show inline comments
 
@@ -6,27 +6,18 @@
 
<h1>{{project.name}}</h1>
 

	
 
{% if project.description %}
 
<h2>Description</h2>
 
{{project.description}}
 
<div>{{project.description}}</div>
 
<hr>
 
{% endif %}
 

	
 
{% if project.entity_set %}
 
<h2>Project Entities</h2>
 

	
 
<table>
 
{% for entity in project.entity_set.all %}
 
  <tr><td>{% html_link 'entity' entity.name entity.id "btn btn-link" %}</td><td>{% html_link 'entity_iptables' '<i class="icon-list"></i>' entity.id "btn btn-link" %}</td></tr>
 
{% endfor %}
 
</table>
 

	
 
<h2>Project Locations</h2>
 

	
 
<ul class="unstyled">
 
{% for location in locations %}
 
  <li><a href="/location/{{location.id}}?project={{project.id}}">{{location}}</a></li>
 
{% endfor %}
 
</ul>
 
{% if location_entities %}
 
<div class="row">
 
  {% for location, entities in location_entities %}
 
  <div class="span4">
 
    <div class="well">{% include "conntrackt/location_widget.html" with location=location entities=entities %}</div>
 
  </div>
 
  {% endfor %}
 
</div>
 
{% endif %}
 

	
 
{% endblock %}
 

	
conntrackt/templates/conntrackt/project_widget.html
Show inline comments
 
new file 100644
 
{% load conntrackt %}
 
  <table class="table table-striped">
 
    <tr><td><strong>{% html_link project.name 'project' project.id class="btn btn-link" title=project.description %}</strong></td><td>{% html_link '<i class="icon-book"></i>' 'project_iptables' project.id class="btn btn-link" %}</td></tr>
 
{% for entity in project.entity_set.all %}
 
    <tr><td>{% html_link entity.name 'entity' entity.id class="btn btn-link" %}</td><td>{% html_link '<i class="icon-list"></i>' 'entity_iptables' entity.id class="btn btn-link" %}</td></tr>
 
{% endfor %}
 
  </table>
 

	
conntrackt/templates/conntrackt/template.html
Show inline comments
 
@@ -26,7 +26,7 @@
 
            <span class="icon-bar"></span>
 
            <span class="icon-bar"></span>
 
          </button>
 
          {% block header_title %}{% html_link 'index' 'Conntrackt' "" "brand" %}{% endblock %}
 
          {% block header_title %}{% html_link 'Conntrackt' 'index' class="brand" %}{% endblock %}
 
          <div class="nav-collapse collapse">
 
              {% block header %}
 
            <ul class="nav">
conntrackt/templatetags/conntrackt.py
Show inline comments
 
@@ -7,23 +7,47 @@ from django.core import urlresolvers
 
# Get an instance of Django's template library.
 
register = template.Library()
 

	
 
@register.inclusion_tag('conntrackt/link.html')
 
def html_link(view, title, identifier = None, html_class = None):
 
@register.inclusion_tag('conntrackt/html_link.html')
 
def html_link(text, view, *args, **kwargs):
 
    """
 
    A small wrapper for showing HTML links.
 
    A small wrapper for showing HTML linnks.
 

	
 
    Positional arguments:
 

	
 
    Arguments:
 
        text - Text that should be used as a link.
 

	
 
        view - View name for which the URL should be shown
 

	
 
    view - View name for which the URL should be shown.
 
        args - Additional positional arguments that will be passed to resolver
 
        for creating the URL.
 

	
 
    title - Title which will be shown as the link.
 
    Keyword arguments:
 

	
 
    identifier - Identifier which is passed to the view for processing.
 
        id - Identifier for the <a> HTML element.
 

	
 
    html_class - Class which should be assigned to the link.
 
        class - Class(es) for the <a> HTML element.
 

	
 
        title - Title for the HTML <a> element.
 

	
 
    """
 

	
 
    return {'view': view, 'identifier': identifier, 'title': title, 'html_class': html_class}
 
    # We'll return a context for rendering with the template. Add the text right
 
    # away.
 
    context = {'text': text}
 

	
 
    # Generate the URL by using the supplied view name and arguments that should
 
    # be passed to the view.
 
    context['url'] = urlresolvers.reverse(view, args=args)
 

	
 
    # Iterate over keyword arguments, and if we support them, just assign them
 
    # to the context as is. Raise an exception if a keyword is unsupported.
 
    for key, value in kwargs.items():
 
        if key in ("class", "title", "id"):
 
            context[key] = value
 
        else:
 
            raise template.TemplateSyntaxError("Unknown argument for 'advhtml_link' tag: %r" % key )
 

	
 
    return context
 

	
 
@register.simple_tag(takes_context = True)
 
def active_link(context, url_name, return_value='active', **kwargs):
conntrackt/urls.py
Show inline comments
 
@@ -6,7 +6,7 @@ from conntrackt.models import Entity, Lo
 
from django.views.generic import DetailView
 

	
 
# Import some app-specific views.
 
from conntrackt.views import IndexView, IptablesView, ProjectView
 
from conntrackt.views import IndexView, IptablesView, ProjectView, get_project_iptables
 

	
 
urlpatterns = patterns(
 
    'conntrackt.views',
 
@@ -23,5 +23,9 @@ urlpatterns = patterns(
 
        name = 'location'),
 
    # View for rendering iptables rules for a specific entity.
 
    url(r'^entity/(?P<pk>\d+)/iptables/$', IptablesView.as_view(), name="entity_iptables"),
 
    # View for rendering zip file with iptables rules for all entities in a project.
 
    url(r'^project/(?P<project_id>\d+)/iptables/$', get_project_iptables, name="project_iptables"),
 
    # View for rendering zip file with iptables rules for all entities in a project for a specific location.
 
    url(r'^project/(?P<project_id>\d+)/location/(?P<location_id>\d+)/iptables/$', get_project_iptables, name="project_location_iptables"),
 
)
 

	
conntrackt/views.py
Show inline comments
 
# For generating ZIP files.
 
from StringIO import StringIO
 
from zipfile import ZipFile, ZIP_DEFLATED
 

	
 
# Django-specific imports.
 
from django.http import HttpResponse
 
from django.shortcuts import render_to_response, get_object_or_404
 
from django.views.generic import TemplateView, DetailView
 

	
 
# Application-specific imports.
 
from conntrackt.models import Project, Entity, Location
 
from conntrackt.stripper import StripperMiddleware
 

	
 
class IndexView(TemplateView):
 
    """
 
@@ -10,6 +19,15 @@ class IndexView(TemplateView):
 
    template_name = 'conntrackt/index.html'
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Returns the context data that should be used for rendering of the
 
        template.
 

	
 
        Adds the 'projects' context object containing all of the projects
 
        available sorted aplhabetically by name.
 
        """
 

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

	
 
        context['projects'] = Project.objects.all().order_by('name')
 
@@ -17,20 +35,144 @@ class IndexView(TemplateView):
 
        return context
 

	
 
class ProjectView(DetailView):
 
    """
 
    Custom view for presenting the project information.
 
    """
 

	
 
    model = Project
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Returns the context data that should be used for rendering of the
 
        template.
 

	
 
        Adds the 'location_entities' context object that contains tuples of the
 
        form '(location, entities)', where location is an instance of a
 
        Location, and entities is a query set of entities that belong to that
 
        particular location, and to related project.
 
        """
 

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

	
 
        context['locations'] = Location.objects.filter(entity__project = context['project']).distinct()
 
        
 
        # Set-up an array that will contaion (location, entities) tuples.
 
        location_entities = []
 

	
 
        # Add the (location, entities) tuple for each location that has entities
 
        # belonging to the related project.
 
        for location in Location.objects.filter(entity__project = self.object).distinct():
 
            entities = Entity.objects.filter(project = self.object, location = location)
 
            location_entities.append((location, entities))
 

	
 
        # Add the (location, entities) tuples to context.
 
        context['location_entities'] = location_entities
 

	
 
        # Finally return the context.
 
        return context
 

	
 
class IptablesView(DetailView):
 
    """
 
    Custom view for rendering iptables rules for a specific entity, and starting
 
    automatic download by browser.
 
    """
 

	
 
    model = Entity
 
    template_name = 'conntrackt/entity_iptables.html'
 

	
 
    def render_to_response(self, context, **kwargs):
 
        """
 
        Renders the template with the given context to response. The response
 
        will be an iptables configuration file that can be used with
 
        iptables-restore.
 

	
 
        Makes sure to set the Content-Disposition of a response in order to
 
        signal the browser it should start download of this view's response
 
        immediately. Also sets the suggested filename for it.
 
        """
 

	
 
        # Call the parent class method first. This will render the template. Set
 
        # the content type to text/plain.
 
        response = super(IptablesView, self).render_to_response(context,
 
                                                                content_type='text/plain', **kwargs)
 
        response['Content-Disposition']="attachment; filename=iptables.conf"
 

	
 
        # Add the Content-Disposition information for the browser, telling the
 
        # browser to download the file with suggested filename.
 
        response['Content-Disposition']="attachment; filename=%s-iptables.conf" % self.object.name.lower().replace(" ", "_")
 

	
 
        # Return the modified response.
 
        return response
 

	
 
def get_project_iptables(request, project_id, location_id = None):
 
    """
 
    Custom view for obtaining iptables for all entities of a project or project
 
    location in a single ZIP file.
 

	
 
    Arguments:
 

	
 
        request - Request object.
 

	
 
        project_id - Unique ID of the project for whose entities the iptables
 
        rules should be generated.
 

	
 
        location_id - Optional unique ID of the project location for whose
 
        entities the iptables rules should be generated. Default is None, which
 
        means generate rules for _all_ entities in a project.
 

	
 
    Returns:
 

	
 
        Response object that contains the ZIP file and Content-Disposition
 
        information.
 
    """
 

	
 
    # Fetch the project.
 
    project = get_object_or_404(Project, pk = project_id)
 

	
 
    # Set-up a string IO object to which we'll write the ZIP file (in-memory).
 
    buff = StringIO()
 

	
 
    # Create a new ZIP file in-memory.
 
    zipped_iptables = ZipFile(buff, "w", ZIP_DEFLATED)
 

	
 
    # Create the response object, setting the mime type so browser could offer
 
    # to open the file with program as well.
 
    response = HttpResponse(mimetype='application/zip')
 

	
 
    # Stripper middleware has to be used in order to remove the excess blank
 
    # lines from rendering (this has to be done manually).
 
    stripper_middleware = StripperMiddleware()
 

	
 
    # If specific location was specified, get the entities that are part of that
 
    # project location only, otherwise fetch all of the project's entities. Also
 
    # set-up the filename that will be suggested to the browser.
 
    if location_id:
 
        location = get_object_or_404(Location, pk=location_id)
 
        entities = project.entity_set.filter(location = location)
 
        filename = '%s_%s-iptables.zip' % (project.name, location.name)
 
    else:
 
        entities = project.entity_set.all()
 
        filename = '%s-iptables.zip' % (project.name)
 

	
 
    # Lower-case the filename, and replace spaces with underscores (_).
 
    filename = filename.lower().replace(" ", "_")
 

	
 
    # Render iptables rules for each entity, placing them in the ZIP archive.
 
    for entity in entities:
 
        entity_iptables = stripper_middleware.process_response(request,render_to_response('conntrackt/entity_iptables.html', {'entity': entity}, mimetype="text/plain"))
 
        zipped_iptables.writestr("%s-iptables.conf" % entity.name.lower().replace(" ", "_"), entity_iptables.content)
 

	
 
    # Close the archive, and flush the buffer.
 
    zipped_iptables.close()
 
    buff.flush()
 

	
 
    # Write the contents of our buffer (ZIP archive) to response content, and
 
    # close the IO string.
 
    response.write(buff.getvalue())
 
    buff.close()
 

	
 
    # Set the Content-Disposition so the browser would know it should download
 
    # the archive, and suggest the filename.
 
    response['Content-Disposition'] = 'attachment; filename="%s"' % filename
 

	
 
    # Finally return the response object.
 
    return response
 

	
0 comments (0 inline, 0 general)