From 92503f6b8c0ef4d34e6a2c0505dd426c8320c0e3 Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:09:34 +0100 Subject: [PATCH 1/8] Create project delete function view --- managers/views.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/managers/views.py b/managers/views.py index 9c9c0378c..3abbbac31 100644 --- a/managers/views.py +++ b/managers/views.py @@ -87,3 +87,12 @@ def get_context_data(self, **kwargs: Any) -> dict: def get_success_url(self) -> str: return reverse("custom-project-detail", kwargs={"pk": self.kwargs["pk"]}) + + +def delete_project(request, pk): + if request.user.user_type == CustomUser.UserType.ADMIN.name: + project = get_object_or_404(Project, pk=pk) + project.delete() + return redirect('custom-projects-list') + else: + return redirect('home') From 23e32970f96c6c00f71ebe04ede62cd18d5155d5 Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:09:56 +0100 Subject: [PATCH 2/8] Add project delete view to urls --- managers/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/managers/urls.py b/managers/urls.py index 34bb2b9db..83af06cb9 100644 --- a/managers/urls.py +++ b/managers/urls.py @@ -13,4 +13,5 @@ url("^projects/create/$", views.ProjectCreateView.as_view(), name="custom-project-create"), url("^projects/(?P[0-9]+)/$", views.ProjectDetail.as_view(), name="custom-project-detail"), url("^projects/(?P[0-9]+)/update/$", views.ProjectUpdateView.as_view(), name="custom-project-update"), + url("^projects/(?P[0-9]+)/delete/$", views.delete_project, name="custom-project-delete"), ] From e7db2faa45e50c9142629bf8d9949eaa013d65cd Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:10:37 +0100 Subject: [PATCH 3/8] Add href to project delete view in managers/project_update.html --- .../managers/scripts/delete_project_popup.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 managers/static/managers/scripts/delete_project_popup.js diff --git a/managers/static/managers/scripts/delete_project_popup.js b/managers/static/managers/scripts/delete_project_popup.js new file mode 100644 index 000000000..2a87b46f6 --- /dev/null +++ b/managers/static/managers/scripts/delete_project_popup.js @@ -0,0 +1,24 @@ +$(function () { + $("#dialog").dialog ({ + modal: true, + autoOpen: false, + buttons : [ + { + text: "No", + click: function () { + $(this).dialog('close'); + } + }, + { + text: "Yes", + click: function () { + window.location.href = delete_project_path; + } + } + ] + }).prev().find(".ui-dialog-titlebar-close").hide (); + + $("#opener").click(function () { + $('#dialog').dialog('open'); + }); +}); From 5126e3551aeeede21ae57d1a2e131303cca16ce7 Mon Sep 17 00:00:00 2001 From: Martyna Date: Wed, 6 Mar 2019 15:31:19 +0100 Subject: [PATCH 4/8] Add test for managers function views --- .../test_unit_managers_function_views.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 managers/tests/test_unit_managers_function_views.py diff --git a/managers/tests/test_unit_managers_function_views.py b/managers/tests/test_unit_managers_function_views.py new file mode 100644 index 000000000..4ded102aa --- /dev/null +++ b/managers/tests/test_unit_managers_function_views.py @@ -0,0 +1,74 @@ +import datetime + +from rest_framework.reverse import reverse +from rest_framework.test import APIRequestFactory + +from django.test import TestCase + +from users.models import CustomUser +from managers import views +from managers.models import Project + + +class DeleteProjectTests(TestCase): + def setUp(self): + self.user = CustomUser( + email="testuser@codepoets.it", + first_name='John', + last_name='Doe', + country='PL', + user_type=CustomUser.UserType.ADMIN.name, + ) + self.user.password = 'newuserpasswd' + self.user.set_password(self.user.password) + self.user.full_clean() + self.user.save() + + self.project = Project( + name='Example Project', + start_date=datetime.datetime.now().date() - datetime.timedelta(days=30), + stop_date=datetime.datetime.now().date(), + terminated=False, + ) + self.project.full_clean() + self.project.save() + self.project.managers.add(self.user) + self.project.members.add(self.user) + self.url = reverse('custom-project-delete', args=(self.project.pk,)) + + def test_delete_project_function_view_should_delete_project_on_admin_type_user_post(self): + project_object_count_befor_post = Project.objects.all().count() + request = APIRequestFactory().post( + path=self.url, + ) + request.user = self.user + response = views.delete_project(request, self.project.pk) + self.assertEqual(response.status_code, 302) + self.assertEqual(Project.objects.all().count(), project_object_count_befor_post - 1) + with self.assertRaises(Project.DoesNotExist) as exception: + Project.objects.get(name=self.project.name) + self.assertTrue("Project matching query does not exist" in str(exception.exception)) + + def test_delete_project_function_view_should_not_delete_project_on_employee_type_user_post(self): + self.user.user_type = CustomUser.UserType.EMPLOYEE.name + project_object_count_befor_post = Project.objects.all().count() + request = APIRequestFactory().post( + path=self.url, + ) + request.user = self.user + response = views.delete_project(request, self.project.pk) + self.assertEqual(response.status_code, 302) + self.assertEqual(Project.objects.all().count(), project_object_count_befor_post) + self.assertTrue(Project.objects.get(name=self.project.name)) + + def test_delete_project_function_view_should_not_delete_project_on_manager_type_user_post(self): + self.user.user_type = CustomUser.UserType.MANAGER.name + project_object_count_befor_post = Project.objects.all().count() + request = APIRequestFactory().post( + path=self.url, + ) + request.user = self.user + response = views.delete_project(request, self.project.pk) + self.assertEqual(response.status_code, 302) + self.assertEqual(Project.objects.all().count(), project_object_count_befor_post) + self.assertTrue(Project.objects.get(name=self.project.name)) From 4d6c90449a60625989141844b5fa93b68a1ee7a8 Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Mon, 8 Apr 2019 01:21:04 +0200 Subject: [PATCH 5/8] Add `parametrized` to requirements --- .../test_unit_managers_function_views.py | 74 ------------------- requirements.lock | 2 + requirements.txt | 1 + 3 files changed, 3 insertions(+), 74 deletions(-) delete mode 100644 managers/tests/test_unit_managers_function_views.py diff --git a/managers/tests/test_unit_managers_function_views.py b/managers/tests/test_unit_managers_function_views.py deleted file mode 100644 index 4ded102aa..000000000 --- a/managers/tests/test_unit_managers_function_views.py +++ /dev/null @@ -1,74 +0,0 @@ -import datetime - -from rest_framework.reverse import reverse -from rest_framework.test import APIRequestFactory - -from django.test import TestCase - -from users.models import CustomUser -from managers import views -from managers.models import Project - - -class DeleteProjectTests(TestCase): - def setUp(self): - self.user = CustomUser( - email="testuser@codepoets.it", - first_name='John', - last_name='Doe', - country='PL', - user_type=CustomUser.UserType.ADMIN.name, - ) - self.user.password = 'newuserpasswd' - self.user.set_password(self.user.password) - self.user.full_clean() - self.user.save() - - self.project = Project( - name='Example Project', - start_date=datetime.datetime.now().date() - datetime.timedelta(days=30), - stop_date=datetime.datetime.now().date(), - terminated=False, - ) - self.project.full_clean() - self.project.save() - self.project.managers.add(self.user) - self.project.members.add(self.user) - self.url = reverse('custom-project-delete', args=(self.project.pk,)) - - def test_delete_project_function_view_should_delete_project_on_admin_type_user_post(self): - project_object_count_befor_post = Project.objects.all().count() - request = APIRequestFactory().post( - path=self.url, - ) - request.user = self.user - response = views.delete_project(request, self.project.pk) - self.assertEqual(response.status_code, 302) - self.assertEqual(Project.objects.all().count(), project_object_count_befor_post - 1) - with self.assertRaises(Project.DoesNotExist) as exception: - Project.objects.get(name=self.project.name) - self.assertTrue("Project matching query does not exist" in str(exception.exception)) - - def test_delete_project_function_view_should_not_delete_project_on_employee_type_user_post(self): - self.user.user_type = CustomUser.UserType.EMPLOYEE.name - project_object_count_befor_post = Project.objects.all().count() - request = APIRequestFactory().post( - path=self.url, - ) - request.user = self.user - response = views.delete_project(request, self.project.pk) - self.assertEqual(response.status_code, 302) - self.assertEqual(Project.objects.all().count(), project_object_count_befor_post) - self.assertTrue(Project.objects.get(name=self.project.name)) - - def test_delete_project_function_view_should_not_delete_project_on_manager_type_user_post(self): - self.user.user_type = CustomUser.UserType.MANAGER.name - project_object_count_befor_post = Project.objects.all().count() - request = APIRequestFactory().post( - path=self.url, - ) - request.user = self.user - response = views.delete_project(request, self.project.pk) - self.assertEqual(response.status_code, 302) - self.assertEqual(Project.objects.all().count(), project_object_count_befor_post) - self.assertTrue(Project.objects.get(name=self.project.name)) diff --git a/requirements.lock b/requirements.lock index 3f765fb44..e9b6cdef8 100644 --- a/requirements.lock +++ b/requirements.lock @@ -38,10 +38,12 @@ more-itertools==5.0.0 mypy==0.700 mypy-extensions==0.4.1 oauthlib==2.1.0 +parameterized==0.7.0 parso==0.3.4 pbr==5.1.2 pexpect==4.6.0 pickleshare==0.7.5 +pipdeptree==0.13.0 pluggy==0.8.1 prompt-toolkit==2.0.9 psycopg2==2.7.5 diff --git a/requirements.txt b/requirements.txt index 03dcfb923..a753d3f4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,6 +18,7 @@ mypy oauthlib psycopg2 pylint +parameterized pytest pytest-cov pytest-django From 95e33143ede6a70b2e6e5b2817c4b49deb93fbfd Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Mon, 8 Apr 2019 01:21:31 +0200 Subject: [PATCH 6/8] Add `is_admin` property for CustomUser model --- users/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/users/models.py b/users/models.py index 1e335daf8..546c078b3 100644 --- a/users/models.py +++ b/users/models.py @@ -128,6 +128,10 @@ def email_user(self, subject: str, message: str, from_email: str = None) -> None """ send_mail(subject, message, from_email, [self.email]) + @property + def is_admin(self) -> bool: + return self.user_type == CustomUser.UserType.ADMIN.name + @receiver(post_save, sender=CustomUser) def update_from_manager_to_employee(sender: "CustomUser", **kwargs: Any) -> None: From 9589d6a9e6e7eb69c4a2c424fe175c99b756b2ee Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Mon, 8 Apr 2019 01:30:34 +0200 Subject: [PATCH 7/8] Change ProjectDeleteView to Django generic view and update template --- managers/templates/managers/project_form.html | 19 ++++++++++++++++- managers/urls.py | 2 +- managers/views.py | 21 ++++++++++++------- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/managers/templates/managers/project_form.html b/managers/templates/managers/project_form.html index b0d7a3c9f..ae81c6a43 100644 --- a/managers/templates/managers/project_form.html +++ b/managers/templates/managers/project_form.html @@ -6,7 +6,6 @@ {% load crispy_forms_tags %} {% block content %} -

@@ -23,8 +22,26 @@

+ +{% if request.user.is_admin and object %} +
+ + + +{% endif %} {% endblock %} {% block extra_script %} {{ form.media }} +{% if request.user.is_admin and object %} + + +{% endif %} {% endblock %} diff --git a/managers/urls.py b/managers/urls.py index 83af06cb9..d950787d0 100644 --- a/managers/urls.py +++ b/managers/urls.py @@ -13,5 +13,5 @@ url("^projects/create/$", views.ProjectCreateView.as_view(), name="custom-project-create"), url("^projects/(?P[0-9]+)/$", views.ProjectDetail.as_view(), name="custom-project-detail"), url("^projects/(?P[0-9]+)/update/$", views.ProjectUpdateView.as_view(), name="custom-project-update"), - url("^projects/(?P[0-9]+)/delete/$", views.delete_project, name="custom-project-delete"), + url("^projects/(?P[0-9]+)/delete/$", views.ProjectDeleteView.as_view(), name="custom-project-delete"), ] diff --git a/managers/views.py b/managers/views.py index 3abbbac31..376d48021 100644 --- a/managers/views.py +++ b/managers/views.py @@ -4,10 +4,13 @@ from django.db.models.functions import Lower from django.db.models.query import QuerySet from django.http import HttpRequest +from django.http import HttpResponse from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect from django.shortcuts import reverse from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView +from django.views.generic import DeleteView from django.views.generic import ListView from django.views.generic import UpdateView from rest_framework import renderers @@ -89,10 +92,14 @@ def get_success_url(self) -> str: return reverse("custom-project-detail", kwargs={"pk": self.kwargs["pk"]}) -def delete_project(request, pk): - if request.user.user_type == CustomUser.UserType.ADMIN.name: - project = get_object_or_404(Project, pk=pk) - project.delete() - return redirect('custom-projects-list') - else: - return redirect('home') +class ProjectDeleteView(DeleteView): + model = Project + + def dispatch(self, request: HttpRequest, *args: Any, **kwargs: Any) -> HttpResponse: + if not request.user.is_admin: + return redirect(reverse("home")) + + return super().dispatch(request, *args, **kwargs) + + def get_success_url(self) -> str: + return reverse("custom-projects-list") From a0cdd95051f81fd43eecb624a9a8f8be454eda63 Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Mon, 8 Apr 2019 01:31:07 +0200 Subject: [PATCH 8/8] Update tests for ProjectDeleteView --- managers/tests/test_views.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/managers/tests/test_views.py b/managers/tests/test_views.py index 92cdfd977..bf41c71a6 100644 --- a/managers/tests/test_views.py +++ b/managers/tests/test_views.py @@ -1,12 +1,14 @@ import datetime from django.shortcuts import reverse +from parameterized import parameterized from managers.factories import ProjectFactory from managers.models import Project from managers.tests.test_api import ProjectTest from managers.views import ProjectCreateView from managers.views import ProjectUpdateView +from users.models import CustomUser class ProjectCreateTests(ProjectTest): @@ -74,3 +76,24 @@ def test_project_update_view_should_update_project_on_post_if_data_is_invalid(se self.assertEqual(response.status_code, 200) self.project.refresh_from_db() self.assertFormError(response, "form", "name", "This field is required.") + + +class DeleteProjectTests(ProjectTest): + def setUp(self): + super().setUp() + self.project = ProjectFactory() + self.url = reverse("custom-project-delete", kwargs={"pk": self.project.pk}) + + def test_delete_project_function_view_should_delete_project_on_admin_type_user_post(self): + response = self.client.post(self.url) + self.assertEqual(response.status_code, 302) + self.assertEqual(Project.objects.all().count(), 0) + + @parameterized.expand([(CustomUser.UserType.EMPLOYEE.name,), (CustomUser.UserType.MANAGER.name,)]) + def test_delete_project_function_view_should_not_delete_project_on_non_admin_request(self, user_type): + self.user.user_type = user_type + self.user.full_clean() + self.user.save() + response = self.client.post(self.url) + self.assertEqual(response.status_code, 302) + self.assertEqual(Project.objects.all().count(), 1)