From 5ea0abc965f357127311c87121db714b28290b1d Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:00:34 +0100 Subject: [PATCH 01/10] Create project_update template --- managers/static/managers/tooltip.css | 33 +++++++++++++++++++ .../templates/managers/project_update.html | 29 ++++++++++++++++ sheetstorm/settings/base.py | 5 +++ users/templates/base.html | 3 ++ 4 files changed, 70 insertions(+) create mode 100644 managers/static/managers/tooltip.css create mode 100644 managers/templates/managers/project_update.html diff --git a/managers/static/managers/tooltip.css b/managers/static/managers/tooltip.css new file mode 100644 index 000000000..7ed16656d --- /dev/null +++ b/managers/static/managers/tooltip.css @@ -0,0 +1,33 @@ +.tool_tip { + position: relative; + display: inline-block; + border: 1px dotted grey; +} + +.tool_tip .tooltip_text { + visibility: hidden; + width: 200px; + background-color: black; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + z-index: 1; + top: -5px; + left: 110%; +} + +.tool_tip .tooltip_text::after { + content: ""; + position: absolute; + top: 50%; + right: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent black transparent transparent; +} +.tool_tip:hover .tooltip_text { + visibility: visible; +} diff --git a/managers/templates/managers/project_update.html b/managers/templates/managers/project_update.html new file mode 100644 index 000000000..87d10abb2 --- /dev/null +++ b/managers/templates/managers/project_update.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} +{% block content %} +{% load i18n %} +{% load rest_framework %} +{% load static %} + +

+ + + + + +  {{project.name}}  +

+ + + +{% endblock %} diff --git a/sheetstorm/settings/base.py b/sheetstorm/settings/base.py index f4f1018cc..fcd131a46 100644 --- a/sheetstorm/settings/base.py +++ b/sheetstorm/settings/base.py @@ -138,6 +138,11 @@ STATIC_URL = '/static/' +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), + "static", +] + AUTH_USER_MODEL = 'users.CustomUser' LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'login' diff --git a/users/templates/base.html b/users/templates/base.html index bf1f1c6b2..932cfd849 100644 --- a/users/templates/base.html +++ b/users/templates/base.html @@ -16,6 +16,9 @@ {% block title %}Sheet storm{% endblock %} + {% block extra_head %} + + {% endblock %}
From da67ba87459d4c7024d3c36274cd881a44b6a9bb Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:02:04 +0100 Subject: [PATCH 02/10] Create ProjectUpdate APIView --- managers/views.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/managers/views.py b/managers/views.py index c0507d33a..0e018886b 100644 --- a/managers/views.py +++ b/managers/views.py @@ -3,6 +3,7 @@ from django.db.models.query import QuerySet from django.http import HttpRequest from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect from django.views.generic import ListView from rest_framework import renderers from rest_framework import viewsets @@ -49,3 +50,32 @@ class ProjectDetail(APIView): def get(_request: HttpRequest, pk: int) -> Response: project = get_object_or_404(Project, pk=pk) return Response({"project": project}) + + +class ProjectUpdate(APIView): + renderer_classes = [renderers.TemplateHTMLRenderer] + template_name = 'managers/project_update.html' + + def get(self, request, pk): + project = get_object_or_404(Project, pk=pk) + project_serializer = ProjectSerializer( + project, + context={'request': request}, + ) + return Response({'serializer': project_serializer, 'project': project}) + + def post(self, request, pk): + project = get_object_or_404(Project, pk=pk) + project_serializer = ProjectSerializer( + project, + data=request.data, + context={'request': request}, + ) + if not project_serializer.is_valid(): + return Response({ + 'serializer': project_serializer, + 'project': project, + 'errors': project_serializer.errors, + }) + project_serializer.save() + return redirect('custom-project-detail', pk=pk) From f6296c8a568a4d8033c011e6d04a4590bb095c74 Mon Sep 17 00:00:00 2001 From: Martyna Date: Fri, 8 Mar 2019 21:02:17 +0100 Subject: [PATCH 03/10] Add project update view to urls --- managers/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/managers/urls.py b/managers/urls.py index 8c90f97ed..13b69b485 100644 --- a/managers/urls.py +++ b/managers/urls.py @@ -11,4 +11,5 @@ url("^api/", include(router.urls)), url("^projects/$", views.ProjectsList.as_view(), name="custom-projects-list"), url("^projects/(?P[0-9]+)/$", views.ProjectDetail.as_view(), name="custom-project-detail"), + url('^projects/(?P[0-9]+)/update/$', views.ProjectUpdate.as_view(), name='custom-project-update'), ] From 7504efbaeb2c91d14413a873e15c67b6f7f9b39d Mon Sep 17 00:00:00 2001 From: Martyna Date: Thu, 3 Jan 2019 14:45:07 +0100 Subject: [PATCH 04/10] Add href to project update view in managers/project_detail.html --- managers/templates/managers/project_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/managers/templates/managers/project_detail.html b/managers/templates/managers/project_detail.html index 11eedd904..3432f7453 100644 --- a/managers/templates/managers/project_detail.html +++ b/managers/templates/managers/project_detail.html @@ -11,7 +11,7 @@

 {{ project.name }}  - + From ce30079225a304123ac06e886a70085d54393898 Mon Sep 17 00:00:00 2001 From: Martyna Date: Wed, 27 Feb 2019 15:44:52 +0100 Subject: [PATCH 05/10] Add tests for ProjectUpdate APIview --- managers/tests/test_unit_managers_apiview.py | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/managers/tests/test_unit_managers_apiview.py b/managers/tests/test_unit_managers_apiview.py index c85c22e0b..bb764468c 100644 --- a/managers/tests/test_unit_managers_apiview.py +++ b/managers/tests/test_unit_managers_apiview.py @@ -36,6 +36,7 @@ def setUp(self): self.custom_projects_list_url = [ reverse("custom-projects-list"), reverse("custom-project-detail", args=(self.project.pk,)), + reverse('custom-project-update', args=(self.project.pk,)), ] @@ -140,3 +141,51 @@ def test_project_detail_view_should_return_404_status_code_on_get_if_project_doe request.user = self.user response = views.ProjectDetail.as_view()(request, self.project.pk + 1) self.assertEqual(response.status_code, 404) + + +class ProjectUpdateTests(ProjectTest): + def test_project_update_view_should_display_project_update_serializer_on_get(self): + request = APIRequestFactory().get(path=self.custom_projects_list_url[2]) + request.user = self.user + response = views.ProjectUpdate.as_view()(request, self.project.pk) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + self.assertEqual(response.data['serializer'].instance, self.project) + + def test_project_update_view_should_return_404_status_code_on_get_if_project_does_not_exist(self): + request = APIRequestFactory().get(path=self.custom_projects_list_url[2]) + request.user = self.user + response = views.ProjectUpdate.as_view()(request, self.project.pk + 1) + self.assertEqual(response.status_code, 404) + + def test_project_update_view_should_update_project_on_post(self): + request = APIRequestFactory().post( + path=self.custom_projects_list_url[2], + data={ + 'name': 'New Example Project Name', + 'start_date': self.project.start_date, + 'managers': [self.user.pk, ], + 'members': [self.user.pk, ], + } + ) + request.user = self.user + response = views.ProjectUpdate.as_view()(request, self.project.pk) + self.project.refresh_from_db() + self.assertEqual(response.status_code, 302) + self.assertEqual('New Example Project Name', self.project.name) + + def test_project_update_view_should_update_project_on_post_if_data_is_invalid(self): + request = APIRequestFactory().post( + path=self.custom_projects_list_url[2], + data={ + 'start_date': self.project.start_date, + 'managers': [self.user.pk, ], + 'members': [self.user.pk, ], + } + ) + request.user = self.user + response = views.ProjectUpdate.as_view()(request, self.project.pk) + self.project.refresh_from_db() + self.assertEqual(response.status_code, 200) + self.assertNotEqual(response.data.get('errors'), None) + self.assertTrue('name' in response.data.get('errors')) From 83ea190e710f33f26e88b193139949b1499a3fcf Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Fri, 5 Apr 2019 02:57:40 +0200 Subject: [PATCH 06/10] Add `django-bootstrap-datepicker-plus` and `django-crispy-forms` to requirements and Django settings --- requirements.lock | 15 +++++++++++++++ requirements.txt | 4 +++- sheetstorm/settings/base.py | 9 ++++----- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/requirements.lock b/requirements.lock index ad870a367..5ca61dafd 100644 --- a/requirements.lock +++ b/requirements.lock @@ -1,15 +1,19 @@ appdirs==1.4.3 atomicwrites==1.2.1 attrs==18.2.0 +backcall==0.1.0 black==19.3b0 certifi==2018.8.24 chardet==3.0.4 Click==7.0 coverage==4.5.2 +decorator==4.3.2 defusedxml==0.5.0 Django==2.1.1 django-allauth==0.37.1 +django-bootstrap-datepicker-plus==3.0.5 django-countries==5.3.2 +django-crispy-forms==1.7.2 django-markdown-deux==1.0.5 django-mock-queries==2.1.1 django-rest-auth==0.9.3 @@ -20,19 +24,28 @@ Faker==1.0.4 flake8==3.7.5 freezegun==0.3.11 idna==2.7 +ipython==7.3.0 +ipython-genutils==0.2.0 +jedi==0.13.3 markdown2==2.3.7 mccabe==0.6.1 mock==2.0.0 model-mommy==1.6.0 more-itertools==5.0.0 oauthlib==2.1.0 +parso==0.3.4 pbr==5.1.2 +pexpect==4.6.0 +pickleshare==0.7.5 pluggy==0.8.1 +prompt-toolkit==2.0.9 psycopg2==2.7.5 psycopg2-binary==2.7.7 +ptyprocess==0.6.0 py==1.7.0 pycodestyle==2.5.0 pyflakes==2.1.1 +Pygments==2.3.1 pytest==4.1.1 pytest-cov==2.6.1 pytest-django==3.4.5 @@ -45,4 +58,6 @@ requests-oauthlib==1.0.0 six==1.11.0 text-unidecode==1.2 toml==0.10.0 +traitlets==4.3.2 urllib3==1.23 +wcwidth==0.1.7 diff --git a/requirements.txt b/requirements.txt index 1e4492bf1..1ed3142a6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,10 @@ certifi chardet defusedxml Django >= 2.1, < 2.2 -django-countries django-allauth +django-bootstrap-datepicker-plus +django-countries +django-crispy-forms django-markdown-deux django-mock-queries django-rest-auth diff --git a/sheetstorm/settings/base.py b/sheetstorm/settings/base.py index fcd131a46..8140447d2 100644 --- a/sheetstorm/settings/base.py +++ b/sheetstorm/settings/base.py @@ -40,6 +40,8 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_countries', + 'bootstrap_datepicker_plus', + 'crispy_forms', 'users', 'markdown_deux', 'rest_framework', @@ -138,11 +140,6 @@ STATIC_URL = '/static/' -STATICFILES_DIRS = [ - os.path.join(BASE_DIR, "static"), - "static", -] - AUTH_USER_MODEL = 'users.CustomUser' LOGIN_REDIRECT_URL = 'home' LOGOUT_REDIRECT_URL = 'login' @@ -187,3 +184,5 @@ 'FR', 'US', ] + +CRISPY_TEMPLATE_PACK = 'bootstrap4' From f7d2047c8b9fdceee1fbc12802d62e617b5b0716 Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Fri, 5 Apr 2019 02:58:19 +0200 Subject: [PATCH 07/10] Add ProjectForm --- managers/forms.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 managers/forms.py diff --git a/managers/forms.py b/managers/forms.py new file mode 100644 index 000000000..e3c5f9d60 --- /dev/null +++ b/managers/forms.py @@ -0,0 +1,11 @@ +from bootstrap_datepicker_plus import DatePickerInput +from django import forms + +from managers.models import Project + + +class ProjectForm(forms.ModelForm): + class Meta: + model = Project + fields = "__all__" + widgets = {"start_date": DatePickerInput(format="%Y-%m-%d"), "stop_date": DatePickerInput(format="%Y-%m-%d")} From 0325873330db62f47475a4cf921cb7e796bcee64 Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Fri, 5 Apr 2019 02:58:52 +0200 Subject: [PATCH 08/10] [M] Add help text to Project.members field and migration for it --- .../migrations/0003_auto_20190405_0058.py | 19 +++++++++++++++++++ managers/models.py | 5 ++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 managers/migrations/0003_auto_20190405_0058.py diff --git a/managers/migrations/0003_auto_20190405_0058.py b/managers/migrations/0003_auto_20190405_0058.py new file mode 100644 index 000000000..c8b233e18 --- /dev/null +++ b/managers/migrations/0003_auto_20190405_0058.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.1 on 2019-04-05 00:58 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('managers', '0002_auto_20190403_1125'), + ] + + operations = [ + migrations.AlterField( + model_name='project', + name='members', + field=models.ManyToManyField(help_text='How to add more employees? Select by CTRL + click', related_name='projects', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/managers/models.py b/managers/models.py index 5f47279de..7ced1e13a 100644 --- a/managers/models.py +++ b/managers/models.py @@ -6,6 +6,7 @@ from django.db.models.query import QuerySet from django.db.models.signals import m2m_changed from django.dispatch import receiver +from django.utils.translation import gettext_lazy as _ from managers.commons.constants import MAX_NAME_LENGTH from users.models import CustomUser @@ -28,7 +29,9 @@ class Project(models.Model): stop_date = models.DateField(null=True, blank=True) terminated = models.BooleanField(default=False) managers = models.ManyToManyField(CustomUser, related_name="manager_projects") - members = models.ManyToManyField(CustomUser, related_name="projects") + members = models.ManyToManyField( + CustomUser, related_name="projects", help_text=_("How to add more employees? Select by CTRL + click") + ) objects = ProjectQuerySet.as_manager() From 06d52963c6e9cba6c059b68002bc228a18d76ed8 Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Fri, 5 Apr 2019 02:59:47 +0200 Subject: [PATCH 09/10] Change ProjectUpdateView to Django generic view and update its template --- managers/static/managers/tooltip.css | 33 ----------------- .../templates/managers/project_update.html | 21 +++++------ managers/urls.py | 2 +- managers/views.py | 36 +++++-------------- users/templates/base.html | 25 ++++++------- 5 files changed, 31 insertions(+), 86 deletions(-) delete mode 100644 managers/static/managers/tooltip.css diff --git a/managers/static/managers/tooltip.css b/managers/static/managers/tooltip.css deleted file mode 100644 index 7ed16656d..000000000 --- a/managers/static/managers/tooltip.css +++ /dev/null @@ -1,33 +0,0 @@ -.tool_tip { - position: relative; - display: inline-block; - border: 1px dotted grey; -} - -.tool_tip .tooltip_text { - visibility: hidden; - width: 200px; - background-color: black; - color: #fff; - text-align: center; - border-radius: 6px; - padding: 5px 0; - position: absolute; - z-index: 1; - top: -5px; - left: 110%; -} - -.tool_tip .tooltip_text::after { - content: ""; - position: absolute; - top: 50%; - right: 100%; - margin-top: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent black transparent transparent; -} -.tool_tip:hover .tooltip_text { - visibility: visible; -} diff --git a/managers/templates/managers/project_update.html b/managers/templates/managers/project_update.html index 87d10abb2..89a15de9a 100644 --- a/managers/templates/managers/project_update.html +++ b/managers/templates/managers/project_update.html @@ -1,8 +1,11 @@ {% extends 'base.html' %} -{% block content %} + {% load i18n %} -{% load rest_framework %} {% load static %} +{% load rest_framework %} +{% load crispy_forms_tags %} + +{% block content %}

@@ -15,15 +18,13 @@

+{% endblock %} +{% block extra_script %} +{{ form.media }} {% endblock %} diff --git a/managers/urls.py b/managers/urls.py index 13b69b485..ecab2276a 100644 --- a/managers/urls.py +++ b/managers/urls.py @@ -11,5 +11,5 @@ url("^api/", include(router.urls)), url("^projects/$", views.ProjectsList.as_view(), name="custom-projects-list"), url("^projects/(?P[0-9]+)/$", views.ProjectDetail.as_view(), name="custom-project-detail"), - url('^projects/(?P[0-9]+)/update/$', views.ProjectUpdate.as_view(), name='custom-project-update'), + url("^projects/(?P[0-9]+)/update/$", views.ProjectUpdateView.as_view(), name="custom-project-update"), ] diff --git a/managers/views.py b/managers/views.py index 0e018886b..1762b77cb 100644 --- a/managers/views.py +++ b/managers/views.py @@ -3,13 +3,15 @@ from django.db.models.query import QuerySet from django.http import HttpRequest from django.shortcuts import get_object_or_404 -from django.shortcuts import redirect +from django.shortcuts import reverse from django.views.generic import ListView +from django.views.generic import UpdateView from rest_framework import renderers from rest_framework import viewsets from rest_framework.response import Response from rest_framework.views import APIView +from managers.forms import ProjectForm from managers.models import Project from managers.serializers import ProjectSerializer from users.models import CustomUser @@ -52,30 +54,10 @@ def get(_request: HttpRequest, pk: int) -> Response: return Response({"project": project}) -class ProjectUpdate(APIView): - renderer_classes = [renderers.TemplateHTMLRenderer] - template_name = 'managers/project_update.html' - - def get(self, request, pk): - project = get_object_or_404(Project, pk=pk) - project_serializer = ProjectSerializer( - project, - context={'request': request}, - ) - return Response({'serializer': project_serializer, 'project': project}) +class ProjectUpdateView(UpdateView): + form_class = ProjectForm + model = Project + template_name = "managers/project_update.html" - def post(self, request, pk): - project = get_object_or_404(Project, pk=pk) - project_serializer = ProjectSerializer( - project, - data=request.data, - context={'request': request}, - ) - if not project_serializer.is_valid(): - return Response({ - 'serializer': project_serializer, - 'project': project, - 'errors': project_serializer.errors, - }) - project_serializer.save() - return redirect('custom-project-detail', pk=pk) + def get_success_url(self) -> None: + return reverse("custom-project-detail", kwargs={"pk": self.kwargs["pk"]}) diff --git a/users/templates/base.html b/users/templates/base.html index 932cfd849..32ceb2b51 100644 --- a/users/templates/base.html +++ b/users/templates/base.html @@ -1,24 +1,16 @@ +{% load static %} +{% load i18n %} +{% load rest_framework %} +{% load user_type_tags %} - {% load static %} - {% load i18n %} - {% load rest_framework %} - {% load user_type_tags %} - - - + {% block title %}Sheet storm{% endblock %} + - - - - {% block title %}Sheet storm{% endblock %} - - {% block extra_head %} - - {% endblock %} + {% block extra_head %}{% endblock %}
@@ -60,5 +52,8 @@ {% endblock %} + + + {% block extra_script %}{% endblock %} From 6133bf7663bd092b89f14206bdad856ed22bb7fd Mon Sep 17 00:00:00 2001 From: Radoslaw Wrzesien Date: Fri, 5 Apr 2019 03:00:06 +0200 Subject: [PATCH 10/10] Update ProjectUpdateView tests --- managers/tests/test_api.py | 143 ++++++++++++++ managers/tests/test_unit_managers_apiview.py | 191 ------------------- managers/tests/test_views.py | 39 ++++ 3 files changed, 182 insertions(+), 191 deletions(-) delete mode 100644 managers/tests/test_unit_managers_apiview.py create mode 100644 managers/tests/test_views.py diff --git a/managers/tests/test_api.py b/managers/tests/test_api.py index 8e7a53f94..edeb5e9d8 100644 --- a/managers/tests/test_api.py +++ b/managers/tests/test_api.py @@ -1,7 +1,12 @@ +import datetime + from django.test import TestCase from rest_framework.reverse import reverse +from rest_framework.test import APIRequestFactory +from managers import views from managers.factories import ProjectFactory +from managers.models import Project from users.models import CustomUser @@ -50,3 +55,141 @@ def test_project_list_view_should_display_project_list_ordered_by_name_regardles self.assertEqual(response.status_code, 200) self.assertEqual([project["name"] for project in response.data], expected_project_order) + + +class ProjectTest(TestCase): + def setUp(self): + super().setUp() + self.user = CustomUser( + email="testuser@codepoets.it", + first_name="John", + last_name="Doe", + country="PL", + user_type=CustomUser.UserType.ADMIN.name, + ) + self.user.set_password("newuserpasswd") + self.user.full_clean() + self.user.save() + self.client.force_login(self.user) + + 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) + + +class CustomProjectsListTests(ProjectTest): + def setUp(self): + super().setUp() + self.url = reverse("custom-projects-list") + + def test_project_list_view_should_display_projects_list_on_get(self): + request = APIRequestFactory().get(path=self.url) + request.user = self.user + response = views.ProjectsList.as_view()(request) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + projects_list = response.context_data["object_list"] + self.assertTrue(self.project in projects_list) + + def test_projects_list_view_should_show_for_managers_only_own_projects(self): + self.user.user_type = CustomUser.UserType.MANAGER.name + manager_project_list = Project.objects.filter(managers__id=self.user.pk) + request = APIRequestFactory().get(path=self.url) + request.user = self.user + response = views.ProjectsList.as_view()(request) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + projects_list = response.context_data["object_list"] + self.assertEqual(list(manager_project_list), list(projects_list)) + + def test_project_list_view_should_display_projects_sorted_by_name_ascending(self): + request = APIRequestFactory().get(path=self.url + "?sort=name") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("name" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_name_descending(self): + request = APIRequestFactory().get(path=self.url + "?sort=-name") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("-name" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_start_date_ascending(self): + request = APIRequestFactory().get(path=self.url + "?sort=start_date") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("start_date" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_start_date_descending(self): + request = APIRequestFactory().get(path=self.url + "?sort=-start_date") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("-start_date" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_stop_date_ascending(self): + request = APIRequestFactory().get(path=self.url + "?sort=stop_date") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("stop_date" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_stop_date_descending(self): + request = APIRequestFactory().get(path=self.url + "?sort=-stop_date") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("-stop_date" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_members_count_ascending(self): + request = APIRequestFactory().get(path=self.url + "?sort=members_count") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("members_count" in projects_list.query.order_by) + + def test_project_list_view_should_display_projects_sorted_by_members_count_descending(self): + request = APIRequestFactory().get(path=self.url + "?sort=-members_count") + request.user = self.user + response = views.ProjectsList.as_view()(request) + projects_list = response.context_data["object_list"] + self.assertTrue(projects_list.ordered) + self.assertTrue("-members_count" in projects_list.query.order_by) + + +class ProjectDetailTests(ProjectTest): + def setUp(self): + super().setUp() + self.url = reverse("custom-project-detail", args=(self.project.pk,)) + + def test_project_detail_view_should_display_project_details_on_get(self): + request = APIRequestFactory().get(path=self.url) + request.user = self.user + response = views.ProjectDetail.as_view()(request, self.project.pk) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + project = response.data["project"] + self.assertEqual(self.project, project) + + def test_project_detail_view_should_return_404_status_code_on_get_if_project_does_not_exist(self): + request = APIRequestFactory().get(path=self.url) + request.user = self.user + response = views.ProjectDetail.as_view()(request, self.project.pk + 1) + self.assertEqual(response.status_code, 404) diff --git a/managers/tests/test_unit_managers_apiview.py b/managers/tests/test_unit_managers_apiview.py deleted file mode 100644 index bb764468c..000000000 --- a/managers/tests/test_unit_managers_apiview.py +++ /dev/null @@ -1,191 +0,0 @@ -import datetime - -from django.test import TestCase -from rest_framework.reverse import reverse -from rest_framework.test import APIRequestFactory - -from managers import views -from managers.models import Project -from users.models import CustomUser - - -class ProjectTest(TestCase): - def setUp(self): - super().setUp() - self.user = CustomUser( - email="testuser@codepoets.it", - first_name="John", - last_name="Doe", - country="PL", - user_type=CustomUser.UserType.ADMIN.name, - ) - self.user.set_password("newuserpasswd") - 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.custom_projects_list_url = [ - reverse("custom-projects-list"), - reverse("custom-project-detail", args=(self.project.pk,)), - reverse('custom-project-update', args=(self.project.pk,)), - ] - - -class ProjectsListTests(ProjectTest): - def test_project_list_view_should_display_projects_list_on_get(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0]) - request.user = self.user - response = views.ProjectsList.as_view()(request) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.project.name) - projects_list = response.context_data["object_list"] - self.assertTrue(self.project in projects_list) - - def test_projects_list_view_should_show_for_managers_only_own_projects(self): - self.user.user_type = CustomUser.UserType.MANAGER.name - manager_project_list = Project.objects.filter(managers__id=self.user.pk) - request = APIRequestFactory().get(path=self.custom_projects_list_url[0]) - request.user = self.user - response = views.ProjectsList.as_view()(request) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.project.name) - projects_list = response.context_data["object_list"] - self.assertEqual(list(manager_project_list), list(projects_list)) - - def test_project_list_view_should_display_projects_sorted_by_name_ascending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=name") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("name" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_name_descending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=-name") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("-name" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_start_date_ascending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=start_date") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("start_date" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_start_date_descending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=-start_date") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("-start_date" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_stop_date_ascending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=stop_date") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("stop_date" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_stop_date_descending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=-stop_date") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("-stop_date" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_members_count_ascending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=members_count") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("members_count" in projects_list.query.order_by) - - def test_project_list_view_should_display_projects_sorted_by_members_count_descending(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[0] + "?sort=-members_count") - request.user = self.user - response = views.ProjectsList.as_view()(request) - projects_list = response.context_data["object_list"] - self.assertTrue(projects_list.ordered) - self.assertTrue("-members_count" in projects_list.query.order_by) - - -class ProjectDetailTests(ProjectTest): - def test_project_detail_view_should_display_project_details_on_get(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[1]) - request.user = self.user - response = views.ProjectDetail.as_view()(request, self.project.pk) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.project.name) - project = response.data["project"] - self.assertEqual(self.project, project) - - def test_project_detail_view_should_return_404_status_code_on_get_if_project_does_not_exist(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[1]) - request.user = self.user - response = views.ProjectDetail.as_view()(request, self.project.pk + 1) - self.assertEqual(response.status_code, 404) - - -class ProjectUpdateTests(ProjectTest): - def test_project_update_view_should_display_project_update_serializer_on_get(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[2]) - request.user = self.user - response = views.ProjectUpdate.as_view()(request, self.project.pk) - self.assertEqual(response.status_code, 200) - self.assertContains(response, self.project.name) - self.assertEqual(response.data['serializer'].instance, self.project) - - def test_project_update_view_should_return_404_status_code_on_get_if_project_does_not_exist(self): - request = APIRequestFactory().get(path=self.custom_projects_list_url[2]) - request.user = self.user - response = views.ProjectUpdate.as_view()(request, self.project.pk + 1) - self.assertEqual(response.status_code, 404) - - def test_project_update_view_should_update_project_on_post(self): - request = APIRequestFactory().post( - path=self.custom_projects_list_url[2], - data={ - 'name': 'New Example Project Name', - 'start_date': self.project.start_date, - 'managers': [self.user.pk, ], - 'members': [self.user.pk, ], - } - ) - request.user = self.user - response = views.ProjectUpdate.as_view()(request, self.project.pk) - self.project.refresh_from_db() - self.assertEqual(response.status_code, 302) - self.assertEqual('New Example Project Name', self.project.name) - - def test_project_update_view_should_update_project_on_post_if_data_is_invalid(self): - request = APIRequestFactory().post( - path=self.custom_projects_list_url[2], - data={ - 'start_date': self.project.start_date, - 'managers': [self.user.pk, ], - 'members': [self.user.pk, ], - } - ) - request.user = self.user - response = views.ProjectUpdate.as_view()(request, self.project.pk) - self.project.refresh_from_db() - self.assertEqual(response.status_code, 200) - self.assertNotEqual(response.data.get('errors'), None) - self.assertTrue('name' in response.data.get('errors')) diff --git a/managers/tests/test_views.py b/managers/tests/test_views.py new file mode 100644 index 000000000..89a1ea9da --- /dev/null +++ b/managers/tests/test_views.py @@ -0,0 +1,39 @@ +from rest_framework.reverse import reverse + +from managers import views +from managers.tests.test_api import ProjectTest + + +class ProjectUpdateViewTestCase(ProjectTest): + def setUp(self): + super().setUp() + self.url = reverse("custom-project-update", kwargs={"pk": self.project.pk}) + self.data = { + "name": "New Example Project Name", + "start_date": self.project.start_date, + "managers": [self.user.pk], + "members": [self.user.pk], + } + + def test_project_update_view_should_display_update_template(self): + response = self.client.get(self.url) + self.assertEqual(response.status_code, 200) + self.assertContains(response, self.project.name) + self.assertTemplateUsed(response, views.ProjectUpdateView.template_name) + + def test_project_update_view_should_return_404_status_code_on_get_if_project_does_not_exist(self): + response = self.client.get(reverse("custom-project-update", kwargs={"pk": self.project.pk + 1})) + self.assertEqual(response.status_code, 404) + + def test_project_update_view_should_update_project_on_post(self): + response = self.client.post(self.url, self.data) + self.assertEqual(response.status_code, 302) + self.project.refresh_from_db() + self.assertEqual(self.project.name, self.data["name"]) + + def test_project_update_view_should_update_project_on_post_if_data_is_invalid(self): + del self.data["name"] + response = self.client.post(self.url, self.data) + self.assertEqual(response.status_code, 200) + self.project.refresh_from_db() + self.assertFormError(response, "form", "name", "This field is required.")