# HG changeset patch # User Branko Majic # Date 2013-07-10 22:24:25 # Node ID 018e7beed87419466c62339fea87d23a76c44f98 # Parent 26c0c45a8480a68b3af569093b35310f4ad905dd CONNT-3: Implemented support for modifying the location information from within Conntrackt application itself. Appropriate tests were implemented as well. diff --git a/conntrackt/templates/conntrackt/index.html b/conntrackt/templates/conntrackt/index.html --- a/conntrackt/templates/conntrackt/index.html +++ b/conntrackt/templates/conntrackt/index.html @@ -18,6 +18,7 @@
{% html_link "Add project" "project_create" class="btn btn-primary" %} + {% html_link "Add location" "location_create" class="btn btn-primary" %}
@@ -28,20 +29,20 @@

Projects

- {% if projects %} - - {% for project in projects %} - - - - - - - {% endfor %} -
{% html_link project.name "project" project.id class="btn btn-link" %}{% html_link '' "project_iptables" project.id class="btn btn-link" %}{% html_link '' "project_update" project.id class="btn btn-link" %}{% html_link '' "project_delete" project.id class="btn btn-link" %}
- {% else %} + {% if projects %} + + {% for project in projects %} + + + + + + + {% endfor %} +
{% html_link project.name "project" project.id class="btn btn-link" %}{% html_link '' "project_iptables" project.id class="btn btn-link" %}{% html_link '' "project_update" project.id class="btn btn-link" %}{% html_link '' "project_delete" project.id class="btn btn-link" %}
+ {% else %} There are no projects defined. - {% endif %} + {% endif %}
@@ -53,6 +54,8 @@ {% for location in locations %} {{location.name}} + {% html_link '' "location_update" location.id class="btn btn-link" %} + {% html_link '' "location_delete" location.id class="btn btn-link" %} {% endfor %} diff --git a/conntrackt/templates/conntrackt/location_confirm_delete.html b/conntrackt/templates/conntrackt/location_confirm_delete.html new file mode 100644 --- /dev/null +++ b/conntrackt/templates/conntrackt/location_confirm_delete.html @@ -0,0 +1,27 @@ +{% extends "conntrackt/base.html" %} + +{# For html_link #} +{% load conntrackt_tags %} +{# For Bootstrapped forms #} +{% load crispy_forms_tags %} + +{% block content %} +
+

Remove location {{location.name}}

+
+
+
+
+
+ {% csrf_token %} + {{ form }} + Are you sure you want to remove this location? +
+
+
+ +
+
+
+
+{% endblock content %} diff --git a/conntrackt/templates/conntrackt/location_create_form.html b/conntrackt/templates/conntrackt/location_create_form.html new file mode 100644 --- /dev/null +++ b/conntrackt/templates/conntrackt/location_create_form.html @@ -0,0 +1,25 @@ +{% extends "conntrackt/base.html" %} + +{# For html_link #} +{% load conntrackt_tags %} +{# For Bootstrapped forms #} +{% load crispy_forms_tags %} + +{% block content %} +
+

Add new location

+
+
+
+
+
+ {% csrf_token %} + {{ form | crispy }} +
+
+ +
+
+
+
+{% endblock content %} diff --git a/conntrackt/templates/conntrackt/location_update_form.html b/conntrackt/templates/conntrackt/location_update_form.html new file mode 100644 --- /dev/null +++ b/conntrackt/templates/conntrackt/location_update_form.html @@ -0,0 +1,25 @@ +{% extends "conntrackt/base.html" %} + +{# For html_link #} +{% load conntrackt_tags %} +{# For Bootstrapped forms #} +{% load crispy_forms_tags %} + +{% block content %} +
+

Edit location {{location.name}}

+
+
+
+
+
+ {% csrf_token %} + {{ form | crispy }} +
+
+ +
+
+
+
+{% endblock content %} diff --git a/conntrackt/tests/test_views.py b/conntrackt/tests/test_views.py --- a/conntrackt/tests/test_views.py +++ b/conntrackt/tests/test_views.py @@ -94,7 +94,7 @@ class IndexViewTest(ViewTest): def test_locations_available(self): """ - Tests if locations are show or not. + Tests if locations are shown or not. """ self.client.login(username="fullperms", password="fullperms") @@ -568,3 +568,181 @@ class ProjectDeleteViewTest(TestCase): follow=True) self.assertContains(response, "Project Test Project 1 has been removed.") + + +class LocationCreateViewTest(TestCase): + + 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="add_location")) + 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("location_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("location_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("location_create")) + + self.assertContains(response, 'class="span6 textinput') + self.assertContains(response, 'class="span6 textarea') + self.assertContains(response, 'placeholder="New Location"') + self.assertContains(response, 'placeholder="Description for new location."') + + +class LocationUpdateViewTest(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_location")) + 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("location_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("location_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("location_update", args=(1,))) + + self.assertContains(response, 'class="span6 textinput') + self.assertContains(response, 'class="span6 textarea') + self.assertContains(response, 'placeholder="Location name"') + self.assertContains(response, 'placeholder="Description for location."') + + 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("location_update", args=(1,))) + + self.assertContains(response, ">Edit location Test Location 1<") + self.assertContains(response, 'value="Test Location 1"') + self.assertContains(response, "This is a test location 1.") + + +class LocationDeleteViewTest(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_location")) + 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("location_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("location_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("location_delete", args=(1,))) + + self.assertContains(response, ">Remove location Test Location 1<") + self.assertContains(response, "Are you sure you want to remove this location?") + + def test_message(self): + """ + Tests if the message gets added when the location is deleted. + """ + + self.client.login(username="fullperms", password="fullperms") + + response = self.client.get(reverse("location_delete", args=(1,))) + + response = self.client.post(reverse("location_delete", args=(1,)), + {'csrfmiddlewaretoken': response.context['request'].META['CSRF_COOKIE']}, + follow=True) + + self.assertContains(response, "Location Test Location 1 has been removed.") diff --git a/conntrackt/urls.py b/conntrackt/urls.py --- a/conntrackt/urls.py +++ b/conntrackt/urls.py @@ -3,13 +3,16 @@ from django.conf.urls import patterns, u from django.contrib.auth.views import login, logout # Application imports. -from .views import IndexView, ProjectView, ProjectCreateView, ProjectUpdateView, ProjectDeleteView, EntityView, entity_iptables, project_iptables +from .views import IndexView, EntityView, entity_iptables, project_iptables +from .views import ProjectView, ProjectCreateView, ProjectUpdateView, ProjectDeleteView +from .views import LocationCreateView, LocationUpdateView, LocationDeleteView 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\d+)/$', ProjectView.as_view(), name='project'), @@ -19,6 +22,15 @@ urlpatterns = patterns( url(r'^project/(?P\d+)/edit/$', ProjectUpdateView.as_view(), name="project_update"), # View for deleting a project. url(r'^project/(?P\d+)/remove/$', ProjectDeleteView.as_view(), name="project_delete"), + + # View for creating a new location. + url(r'^location/add/$', LocationCreateView.as_view(), name="location_create"), + # View for updating an existing location. + url(r'^location/(?P\d+)/edit/$', LocationUpdateView.as_view(), name="location_update"), + # View for deleting a location. + url(r'^location/(?P\d+)/remove/$', LocationDeleteView.as_view(), name="location_delete"), + + # View for showing information about an entity. url(r'^entity/(?P\d+)/$', EntityView.as_view(), name='entity'), diff --git a/conntrackt/views.py b/conntrackt/views.py --- a/conntrackt/views.py +++ b/conntrackt/views.py @@ -323,3 +323,97 @@ class ProjectDeleteView(MultiplePermissi 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) + + +class LocationCreateView(MultiplePermissionsRequiredMixin, CreateView): + """ + View for creating a new location. + """ + + model = Location + template_name_suffix = "_create_form" + + # Required permissions. + permissions = { + "all": ("conntrackt.add_location",), + } + + # Raise authorisation denied exception for unmet permissions. + raise_exception = True + + success_url = reverse_lazy("index") + + 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(LocationCreateView, self).get_form(form_class) + form.fields["name"].widget.attrs["class"] = "span6" + form.fields["name"].widget.attrs["placeholder"] = "New Location" + form.fields["description"].widget.attrs["class"] = "span6" + form.fields["description"].widget.attrs["placeholder"] = "Description for new location." + + return form + + +class LocationUpdateView(MultiplePermissionsRequiredMixin, UpdateView): + """ + View for modifying an existing location. + """ + + model = Location + template_name_suffix = "_update_form" + + # Required permissions. + permissions = { + "all": ("conntrackt.change_location",), + } + + # Raise authorisation denied exception for unmet permissions. + raise_exception = True + + success_url = reverse_lazy("index") + + 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(LocationUpdateView, self).get_form(form_class) + form.fields["name"].widget.attrs["class"] = "span6" + form.fields["name"].widget.attrs["placeholder"] = "Location name" + form.fields["description"].widget.attrs["class"] = "span6" + form.fields["description"].widget.attrs["placeholder"] = "Description for location." + + return form + + +class LocationDeleteView(MultiplePermissionsRequiredMixin, DeleteView): + """ + View for deleting a location. + """ + + model = Location + + # Required permissions. + permissions = { + "all": ("conntrackt.delete_location",), + } + + # 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 + location deletion. + """ + + messages.success(self.request, "Location %s has been removed." % self.get_object().name, extra_tags="alert alert-success") + + return super(LocationDeleteView, self).post(*args, **kwargs) diff --git a/docs/conf.py b/docs/conf.py --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys +import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the