Changeset - 509ed963b5e1
[Not reviewed]
default
1 11 3
Branko Majic (branko) - 11 years ago 2013-07-07 15:57:32
branko@majic.rs
CONNT-2: Implemented project editing, and removal. Added tests for project add/edit/remove views. Added rendering of messages in the base template. Renamed the project_add view to project_create in order to follow the view naming schema. Cleaned-up some PEP8 errors (unrelated to issue).
15 files changed with 301 insertions and 46 deletions:
0 comments (0 inline, 0 general)
conntrackt/models.py
Show inline comments
 
@@ -102,13 +102,12 @@ class Entity(models.Model):
 
    class Meta:
 
        # Fix the plural form used by Django.
 
        verbose_name_plural = 'entities'
 
        # Enforce uniqueness of entity name in a project.
 
        unique_together = ("name", "project")
 

	
 

	
 
    def __unicode__(self):
 
        """
 
        Returns:
 
          String representation of an entity. This identifier contains name of
 
          entity, its project name, and location name.
 
        """
conntrackt/templates/conntrackt/base.html
Show inline comments
 
@@ -46,14 +46,19 @@
 
        </div>
 
      </div>
 
    </div>
 

	
 
    <div class="container">
 
      <div id="content" class="block">
 
      {% block content %}
 
      {% endblock %}
 
        {% if messages %}
 
          {% for message in messages %}
 
            <div class="{{message.tags}}">{{ message }}</div>
 
          {% endfor %}
 
        {% endif %}
 
        {% block content %}
 
        {% endblock %}
 
      </div>
 
    </div>
 

	
 
    <script src="/static/jquery-min.js"></script>
 
    <script src="/static/bootstrap/js/bootstrap.js"></script>
 
  </body>
conntrackt/templates/conntrackt/index.html
Show inline comments
 
@@ -10,13 +10,13 @@
 
<div class="row">
 
    <div class="span12">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. Clicking on an entity inside of a project will take you to the summary page of an entity.</div>
 
</div>
 
<hr>
 
<div class="row">
 
  <div class="span12">
 
    {% html_link "Add project" "project_add" class="btn btn-primary" %}
 
    {% html_link "Add project" "project_create" class="btn btn-primary" %}
 
  </div>
 
</div>
 
<hr>
 
<div class="row">
 
{% if projects %}
 
  {% for project in projects %}
conntrackt/templates/conntrackt/project_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 project {{project.name}}</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 project?
 
      </div>
 
      <hr>
 
      <div class="controls">
 
        <button type="submit" class="btn btn-primary">Remove</button>
 
      </div>
 
    </form>
 
  </div>
 
</div>
 
{% endblock content %}
conntrackt/templates/conntrackt/project_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 project</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">Add</button>
 
      </div>
 
    </form>
 
  </div>
 
</div>
 
{% endblock content %}
conntrackt/templates/conntrackt/project_detail.html
Show inline comments
 
@@ -11,12 +11,19 @@
 
    {{project.description}}
 
  </div>
 
</div>
 
<hr>
 
{% endif %}
 

	
 
<div class="row">
 
  <div class="span12">
 
    {% html_link "Edit" "project_update" project.id class="btn btn-primary" %}
 
    {% html_link "Remove" "project_delete" project.id class="btn btn-primary" %}
 
  </div>
 
</div>
 
<hr>
 
{% 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>
conntrackt/templates/conntrackt/project_form.html
Show inline comments
 
deleted file
conntrackt/templates/conntrackt/project_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 project {{project.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/tests/test_models.py
Show inline comments
 
@@ -94,13 +94,12 @@ class EntityTest(TestCase):
 

	
 
        ent = Entity.objects.get(name="Test Entity 1")
 
        representation = "Test Entity 1 (Test Project 1 - Test Location 1)"
 

	
 
        self.assertEqual(str(ent), representation)
 

	
 

	
 
    def test_unique_name(self):
 
        """
 
        Test if unique entity name is enforced across same project.
 
        """
 

	
 
        entity1 = Entity.objects.get(pk=1)
 
@@ -128,17 +127,16 @@ class InterfaceTest(TestCase):
 
        """
 

	
 
        entity = Entity.objects.get(pk=1)
 

	
 
        interface = entity.interface_set.get(pk=1)
 

	
 
        duplicate = Interface(name=interface.name, description = "Duplicate interface.", entity=entity, address="10.10.10.10", netmask="255.255.255.255")
 
        duplicate = Interface(name=interface.name, description="Duplicate interface.", entity=entity, address="10.10.10.10", netmask="255.255.255.255")
 

	
 
        self.assertRaises(IntegrityError, duplicate.save)
 

	
 

	
 
    def test_representation_single(self):
 
        """
 
        Test representation of single IP address.
 
        """
 

	
 
        interface = Entity.objects.get(name="Test Entity 1").interface_set.get(name="eth0")
conntrackt/tests/test_views.py
Show inline comments
 
@@ -376,43 +376,170 @@ class ProjectCreateViewTest(TestCase):
 
        # Set-up users with different view permissions.
 
        self.user = {}
 
        self.user["fullperms"] = User.objects.create_user("fullperms", "fullperms@example.com", "fullperms")
 
        self.user["fullperms"].user_permissions.add(Permission.objects.get(codename="add_project"))
 
        self.user["noperms"] = User.objects.create_user("noperms", "noperms@example.com", "noperms")
 

	
 

	
 
    def test_permission_denied(self):
 
        """
 
        Tests if permission will be denied for client without sufficient privileges.
 
        """
 

	
 
        self.client.login(username="noperms", password="noperms")
 
        
 
        response = self.client.get(reverse("project_add"))
 
 
 

	
 
        response = self.client.get(reverse("project_create"))
 

	
 
        self.assertContains(response, "You have insufficient privileges to access this resource. Please contact your local system administrator if you believe you should have been granted access.", status_code=403)
 

	
 
    def test_permission_granted(self):
 
        """
 
        Tests if permission will be granted for user with correct privileges.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_add"))
 
        response = self.client.get(reverse("project_create"))
 

	
 
        self.assertEqual(response.status_code, 200)
 

	
 
    def test_form_styling(self):
 
        """
 
        Tests if proper form styling is being applied.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_create"))
 

	
 
        self.assertContains(response, 'class="span6 textinput')
 
        self.assertContains(response, 'class="span6 textarea')
 
        self.assertContains(response, 'placeholder="New Project"')
 
        self.assertContains(response, 'placeholder="Description for new project."')
 

	
 

	
 
class ProjectUpdateViewTest(TestCase):
 

	
 
    fixtures = ['test-data.json']
 

	
 
    def setUp(self):
 
        # Set-up web client.
 
        self.client = Client()
 

	
 
        # Set-up users with different view permissions.
 
        self.user = {}
 
        self.user["fullperms"] = User.objects.create_user("fullperms", "fullperms@example.com", "fullperms")
 
        self.user["fullperms"].user_permissions.add(Permission.objects.get(codename="change_project"))
 
        self.user["noperms"] = User.objects.create_user("noperms", "noperms@example.com", "noperms")
 

	
 
    def test_permission_denied(self):
 
        """
 
        Tests if permission will be denied for client without sufficient privileges.
 
        """
 

	
 
        self.client.login(username="noperms", password="noperms")
 

	
 
        response = self.client.get(reverse("project_update", args=(1,)))
 

	
 
        self.assertContains(response, "You have insufficient privileges to access this resource. Please contact your local system administrator if you believe you should have been granted access.", status_code=403)
 

	
 
    def test_permission_granted(self):
 
        """
 
        Tests if permission will be granted for user with correct privileges.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_update", args=(1,)))
 

	
 
        self.assertEqual(response.status_code, 200)
 

	
 
    def test_form_styling(self):
 
        """
 
        Tests if proper form styling is being applied.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_add"))
 
        response = self.client.get(reverse("project_update", args=(1,)))
 

	
 
        self.assertContains(response, 'class="span6 textinput')
 
        self.assertContains(response, 'class="span6 textarea')
 
        self.assertContains(response, 'placeholder="New Project"')
 
        self.assertContains(response, 'placeholder="Description for new project."')
 
        self.assertContains(response, 'placeholder="Project name"')
 
        self.assertContains(response, 'placeholder="Description for project."')
 

	
 
    def test_content(self):
 
        """
 
        Tests if the form comes pre-populated with proper content.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_update", args=(1,)))
 

	
 
        self.assertContains(response, ">Edit project Test Project 1<")
 
        self.assertContains(response, 'value="Test Project 1"')
 
        self.assertContains(response, "This is a test project 1.")
 

	
 

	
 
class ProjectDeleteViewTest(TestCase):
 

	
 
    fixtures = ['test-data.json']
 

	
 
    def setUp(self):
 
        # Set-up web client.
 
        self.client = Client()
 

	
 
        # Set-up users with different view permissions.
 
        self.user = {}
 
        self.user["fullperms"] = User.objects.create_user("fullperms", "fullperms@example.com", "fullperms")
 
        self.user["fullperms"].user_permissions.add(Permission.objects.get(codename="delete_project"))
 
        self.user["fullperms"].user_permissions.add(Permission.objects.get(codename="view"))
 
        self.user["noperms"] = User.objects.create_user("noperms", "noperms@example.com", "noperms")
 

	
 
    def test_permission_denied(self):
 
        """
 
        Tests if permission will be denied for client without sufficient privileges.
 
        """
 

	
 
        self.client.login(username="noperms", password="noperms")
 

	
 
        response = self.client.get(reverse("project_delete", args=(1,)))
 

	
 
        self.assertContains(response, "You have insufficient privileges to access this resource. Please contact your local system administrator if you believe you should have been granted access.", status_code=403)
 

	
 
    def test_permission_granted(self):
 
        """
 
        Tests if permission will be granted for user with correct privileges.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_delete", args=(1,)))
 

	
 
        self.assertEqual(response.status_code, 200)
 

	
 
    def test_content(self):
 
        """
 
        Tests if the form comes pre-populated with proper content.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_delete", args=(1,)))
 

	
 
        self.assertContains(response, ">Remove project Test Project 1<")
 
        self.assertContains(response, "Are you sure you want to remove this project?")
 

	
 
    def test_message(self):
 
        """
 
        Tests if the message gets added when the project is deleted.
 
        """
 

	
 
        self.client.login(username="fullperms", password="fullperms")
 

	
 
        response = self.client.get(reverse("project_delete", args=(1,)))
 

	
 
        response = self.client.post(reverse("project_delete", args=(1,)),
 
                                    {'csrfmiddlewaretoken': response.context['request'].META['CSRF_COOKIE']},
 
                                    follow=True)
 

	
 
        self.assertContains(response, "Project Test Project 1 has been removed.")
conntrackt/urls.py
Show inline comments
 
# Django imports.
 
from django.conf.urls import patterns, url
 
from django.contrib.auth.views import login, logout
 

	
 
# Application imports.
 
from .views import IndexView, ProjectView, ProjectCreateView, EntityView, entity_iptables, project_iptables
 
from .views import IndexView, ProjectView, ProjectCreateView, ProjectUpdateView, ProjectDeleteView, EntityView, entity_iptables, project_iptables
 

	
 

	
 
urlpatterns = patterns(
 
    'conntrackt.views',
 
    # Homepage/index view.
 
    url(r'^$', IndexView.as_view(), name="index"),
 
    # View for showing information about a project.
 
    url(r'^project/(?P<pk>\d+)/$', ProjectView.as_view(),
 
        name='project'),
 
    # View for creating a new project.
 
    url(r'^project/add/$', ProjectCreateView.as_view(), name="project_add"),
 
    url(r'^project/add/$', ProjectCreateView.as_view(), name="project_create"),
 
    # View for updating an existing project.
 
    url(r'^project/(?P<pk>\d+)/edit/$', ProjectUpdateView.as_view(), name="project_update"),
 
    # View for deleting a project.
 
    url(r'^project/(?P<pk>\d+)/remove/$', ProjectDeleteView.as_view(), name="project_delete"),
 
    # View for showing information about an entity.
 
    url(r'^entity/(?P<pk>\d+)/$', EntityView.as_view(),
 
        name='entity'),
 
    # 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
 
# 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
 
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
 
@@ -234,13 +236,14 @@ def project_iptables(request, project_id
 
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.
 
@@ -257,6 +260,64 @@ class ProjectCreateView(MultiplePermissi
 
        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)
projtest/projtest/settings.py
Show inline comments
 
@@ -7,13 +7,13 @@ ADMINS = (
 
)
 

	
 
MANAGERS = ADMINS
 

	
 
DATABASES = {
 
    'default': {
 
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
 
        'ENGINE': 'django.db.backends.sqlite3',  # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
 
        'NAME': 'projtest.db',                      # Or path to database file if using sqlite3.
 
        'USER': '',                      # Not used with sqlite3.
 
        'PASSWORD': '',                  # Not used with sqlite3.
 
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
 
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
 
    }
 
@@ -164,10 +164,10 @@ LOGGING = {
 
            'propagate': True,
 
        },
 
    }
 
}
 

	
 
# View that should be called for log-in action.
 
LOGIN_URL="login"
 
LOGIN_URL = "login"
 

	
 
# Use custom test runner.
 
TEST_RUNNER = 'discover_runner.DiscoverRunner'
projtest/projtest/urls.py
Show inline comments
 
@@ -10,17 +10,16 @@ admin.autodiscover()
 

	
 
urlpatterns = patterns(
 
    '',
 
    # Examples:
 
    # url(r'^$', 'projtest.views.home', name='home'),
 
    # url(r'^projtest/', include('projtest.foo.urls')),
 
    url(r'^$', lambda r : HttpResponseRedirect('conntrackt/')),
 
    url(r'^$', lambda r: HttpResponseRedirect('conntrackt/')),
 
    url(r'^conntrackt/', include('conntrackt.urls')),
 

	
 

	
 
    # Uncomment the admin/doc line below to enable admin documentation:
 
    # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
 

	
 
    # Uncomment the next line to enable the admin:
 
    url(r'^admin/', include(admin.site.urls)),
 
)
 

	
setup.py
Show inline comments
 
@@ -23,13 +23,13 @@ setup(
 
    install_requires=INSTALL_REQUIREMENTS,
 
    tests_require=TEST_REQUIREMENTS,
 
    classifiers=[
 
        'Environment :: Web Environment',
 
        'Framework :: Django',
 
        'Intended Audience :: Developers',
 
        'License :: OSI Approved :: GPLv3', # example license
 
        'License :: OSI Approved :: GPLv3',
 
        'Operating System :: OS Independent',
 
        'Programming Language :: Python',
 
        'Programming Language :: Python :: 2.7',
 
        'Topic :: Internet :: WWW/HTTP',
 
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
 
    ],
0 comments (0 inline, 0 general)