Files @ 26c0c45a8480
Branch filter:

Location: conntrackt/conntrackt/views.py

branko
CONNT-7: Modified the index view to show listing of projects and entities in two columns. Updated the index test accordingly.
# Standard library imports.
from StringIO import StringIO
from zipfile import ZipFile, ZIP_DEFLATED

# Django imports.
from django.contrib.auth.decorators import permission_required
from django.contrib import messages
from django.core.urlresolvers import reverse_lazy
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

# Third-party application imports.
from braces.views import MultiplePermissionsRequiredMixin

# Application imports.
from .models import Project, Entity, Location
from .utils import generate_entity_iptables


class IndexView(MultiplePermissionsRequiredMixin, TemplateView):
    """
    Custom view used for rendering the index page.
    """

    template_name = 'conntrackt/index.html'

    # Required permissions.
    permissions = {
        "all": ("conntrackt.view",),
        }
    # Raise authorisation denied exception for unmet permissions.
    raise_exception = True

    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)

        # Store information about all projcts in context.
        context['projects'] = Project.objects.all().order_by('name')

        # Store information about all locations in context.
        context['locations'] = Location.objects.all().order_by('name')

        return context


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

    model = Project

    permissions = {
        "all": ("conntrackt.view",),
        }
    # Raise authorisation denied exception for unmet permissions.
    raise_exception = True

    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)

        # 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 EntityView(MultiplePermissionsRequiredMixin, DetailView):
    """
    Custom view for presenting entity information.
    """

    # Optimise the query to fetch the related data from reverse relationships.
    queryset = Entity.objects.all()
    queryset = queryset.prefetch_related('interface_set__destination_set__source__entity')
    queryset = queryset.prefetch_related('interface_set__destination_set__destination__entity')
    queryset = queryset.prefetch_related('interface_set__source_set__source__entity')
    queryset = queryset.prefetch_related('interface_set__source_set__destination__entity')

    # Required permissions.
    permissions = {
        "all": ("conntrackt.view",),
        }
    # Raise authorisation denied exception for unmet permissions.
    raise_exception = True

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

        Adds the 'entity_iptables' context object that contains full iptables
        rules generated for the entity.
        """

        # Call the parent class method.
        context = super(DetailView, self).get_context_data(**kwargs)

        # Add the rendered iptables rules to the context.
        context['entity_iptables'] = generate_entity_iptables(self.object)

        return context


@permission_required("conntrackt.view", raise_exception=True)
def entity_iptables(request, pk):
    """
    Custom view that returns response containing iptables rules generated for an
    entity.

    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.

    Arguments:

        pk - Primary key of the Entity object for which the rules should be
        generated.

    Returns:

        Response object that contains the iptables rules for specified entity.
    """

    # Fetch the entity, and construct the response with iptables rules as
    # content.
    entity = get_object_or_404(Entity, pk=pk)
    content = generate_entity_iptables(entity)
    response = HttpResponse(content, mimetype='text/plain')

    # 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" % entity.name.lower().replace(" ", "_")

    return response


@permission_required("conntrackt.view", raise_exception=True)
def 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')

    # 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.lower().replace(" ", "_"), location.name.lower().replace(" ", "_"))
    else:
        entities = project.entity_set.all()
        filename = '%s-iptables.zip' % (project.name.lower().replace(" ", "_"))

    # Render iptables rules for each entity, placing them in the ZIP archive.
    for entity in entities:
        entity_iptables = generate_entity_iptables(entity)
        zipped_iptables.writestr("%s-iptables.conf" % entity.name.lower().replace(" ", "_"), entity_iptables)

    # 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


class ProjectCreateView(MultiplePermissionsRequiredMixin, CreateView):
    """
    View for creating a new project.
    """

    model = Project
    template_name_suffix = "_create_form"

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

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

    def get_form(self, form_class):
        """
        Implements an override for the default form constructed for the create
        view that includes some better styling of input widgets.
        """

        form = super(ProjectCreateView, self).get_form(form_class)
        form.fields["name"].widget.attrs["class"] = "span6"
        form.fields["name"].widget.attrs["placeholder"] = "New Project"
        form.fields["description"].widget.attrs["class"] = "span6"
        form.fields["description"].widget.attrs["placeholder"] = "Description for new project."

        return form


class ProjectUpdateView(MultiplePermissionsRequiredMixin, UpdateView):
    """
    View for modifying an existing project.
    """

    model = Project
    template_name_suffix = "_update_form"

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

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

    def get_form(self, form_class):
        """
        Implements an override for the default form constructed for the create
        view that includes some better styling of input widgets.
        """

        form = super(ProjectUpdateView, self).get_form(form_class)
        form.fields["name"].widget.attrs["class"] = "span6"
        form.fields["name"].widget.attrs["placeholder"] = "Project name"
        form.fields["description"].widget.attrs["class"] = "span6"
        form.fields["description"].widget.attrs["placeholder"] = "Description for project."

        return form


class ProjectDeleteView(MultiplePermissionsRequiredMixin, DeleteView):
    """
    View for deleting a project.
    """

    model = Project

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

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

    success_url = reverse_lazy("index")

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

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

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