diff --git a/employees/common/strings.py b/employees/common/strings.py
index 7af4bc42a..127b8cd75 100644
--- a/employees/common/strings.py
+++ b/employees/common/strings.py
@@ -14,6 +14,10 @@
class ReportListStrings(Enum):
PAGE_TITLE = ugettext_lazy("Reports")
CREATE_REPORT_BUTTON = ugettext_lazy("Create")
+ JOIN_PROJECT_BUTTON = ugettext_lazy("Join project")
+ JOIN_POPUP_HEADER = ugettext_lazy("Join project")
+ JOIN_POPUP_YES = ugettext_lazy("Join")
+ JOIN_POPUP_NO = ugettext_lazy("Cancel")
DATE_COLUMN_HEADER = ugettext_lazy("Date")
PROJECT_COLUMN_HEADER = ugettext_lazy("Project")
WORK_HOURS_COLUMN_HEADER = ugettext_lazy("Work hours")
diff --git a/employees/forms.py b/employees/forms.py
new file mode 100644
index 000000000..6c2aa5c48
--- /dev/null
+++ b/employees/forms.py
@@ -0,0 +1,12 @@
+from django import forms
+from django.db.models import QuerySet
+
+
+class ProjectJoinForm(forms.Form):
+
+ projects = forms.ChoiceField(choices=[])
+
+ def __init__(self, queryset, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ assert isinstance(queryset, QuerySet)
+ self.fields['projects'].choices = [(project.id, project.name) for project in queryset]
diff --git a/employees/static/employees/scripts/join_popup_window.js b/employees/static/employees/scripts/join_popup_window.js
new file mode 100644
index 000000000..f66fa7842
--- /dev/null
+++ b/employees/static/employees/scripts/join_popup_window.js
@@ -0,0 +1,18 @@
+$(function () {
+ $("#dialog").dialog ({
+ modal: true,
+ autoOpen: false,
+ buttons : [
+ {
+ text: join_discard_text,
+ click: function () {
+ $(this).dialog('close');
+ }
+ }
+ ]
+ }).prev().find(".ui-dialog-titlebar-close").hide ();
+
+ $("#opener").click(function () {
+ $("#dialog").dialog('open');
+ });
+});
\ No newline at end of file
diff --git a/employees/templates/employees/report_list.html b/employees/templates/employees/report_list.html
index b9077a1d8..1bce1da11 100644
--- a/employees/templates/employees/report_list.html
+++ b/employees/templates/employees/report_list.html
@@ -4,10 +4,21 @@
{% load rest_framework %}
{% load static %}
-
+
+
diff --git a/employees/tests/test_unit_project_join_form.py b/employees/tests/test_unit_project_join_form.py
new file mode 100644
index 000000000..b170ec6d5
--- /dev/null
+++ b/employees/tests/test_unit_project_join_form.py
@@ -0,0 +1,25 @@
+import datetime
+from django.test import TestCase
+
+from employees.forms import ProjectJoinForm
+from managers.models import Project
+
+
+class ProjectJoinFormTests(TestCase):
+ def test_project_join_form_should_create_choice_field_with_project_name_and_id_based_on_queryset_provided_in_constructor(self):
+ queryset_length = 10
+ for i in range(queryset_length):
+ project = Project(
+ name=f"Test Project {i}",
+ start_date=datetime.datetime.now(),
+ )
+ project.full_clean()
+ project.save()
+ queryset = Project.objects.all()
+ form = ProjectJoinForm(queryset)
+ choices = form.fields['projects'].choices
+ self.assertIsNotNone(choices)
+ self.assertEqual(len(choices), queryset_length)
+ for i in range(queryset_length):
+ self.assertEqual(choices[i][0], queryset[i].id)
+ self.assertEqual(choices[i][1], queryset[i].name)
diff --git a/employees/tests/test_unit_report_viewset.py b/employees/tests/test_unit_report_viewset.py
index c3e1ee43f..60e448b91 100644
--- a/employees/tests/test_unit_report_viewset.py
+++ b/employees/tests/test_unit_report_viewset.py
@@ -315,6 +315,78 @@ def test_get_queryset_method_should_return_queryset_containing_all_of_current_us
self.assertEqual(queryset[1], self.report)
self.assertEqual(queryset[2], other_report_2)
+ def test_custom_report_list_add_project_method_should_register_current_user_as_project_member(self):
+ new_project = Project(
+ name="New Project",
+ start_date=datetime.datetime.now(),
+ )
+ new_project.full_clean()
+ new_project.save()
+ request = APIRequestFactory().get(path=self.url)
+ request.user = self.user
+ view = ReportList()
+ view.request = request
+ serializer = view._create_serializer()
+ view._add_project(serializer, new_project)
+ self.assertTrue(self.user in new_project.members.all())
+ self.assertEqual(serializer.fields['project'].initial, new_project)
+
+ def test_custom_report_list_create_serializer_method_should_return_serializer_with_project_field_options_containing_only_projects_to_which_current_user_belongs(self):
+ new_project = Project(
+ name="New Project",
+ start_date=datetime.datetime.now(),
+ )
+ new_project.full_clean()
+ new_project.save()
+ new_project.members.add(self.user)
+ new_project.full_clean()
+ new_project.save()
+ request = APIRequestFactory().get(path=self.url)
+ request.user = self.user
+ view = ReportList()
+ view.request = request
+ serializer = view._create_serializer()
+ self.assertTrue(new_project in serializer.fields['project'].queryset)
+ self.assertTrue(self.project not in serializer.fields['project'].queryset)
+
+ def test_custom_report_list_view_should_add_user_to_project_selected_in_project_join_form_on_join(self):
+ new_project = Project(
+ name="New Project",
+ start_date=datetime.datetime.now(),
+ )
+ new_project.full_clean()
+ new_project.save()
+ request = APIRequestFactory().post(
+ path=self.url,
+ data={
+ 'projects': new_project.id,
+ 'join': "join",
+ }
+ )
+ request.user = self.user
+ response = ReportList.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(self.user in new_project.members.all())
+ self.assertEqual(response.data['serializer'].fields['project'].initial, new_project)
+
+ def test_custom_report_list_view_should_not_add_user_to_project_selected_in_project_join_form_on_post(self):
+ new_project = Project(
+ name="New Project",
+ start_date=datetime.datetime.now(),
+ )
+ new_project.full_clean()
+ new_project.save()
+ request = APIRequestFactory().post(
+ path=self.url,
+ data={
+ 'projects': new_project.id,
+ }
+ )
+ request.user = self.user
+ response = ReportList.as_view()(request)
+ self.assertEqual(response.status_code, 200)
+ self.assertFalse(self.user in new_project.members.all())
+
class ReportDetailTests(TestCase):
def setUp(self):
@@ -334,6 +406,7 @@ def setUp(self):
)
self.project.full_clean()
self.project.save()
+ self.project.members.add(self.user)
self.report = Report(
date=datetime.datetime.now().date(),
@@ -417,6 +490,44 @@ def test_custom_report_detail_view_should_not_update_report_on_post_if_form_is_i
self.assertIsNotNone(response.data['errors'])
self.assertNotEqual(new_description, self.report.description)
+ def test_custom_report_detail_view_should_not_update_report_if_author_is_not_a_member_of_selected_project(self):
+ other_project = Project(
+ name="Other Project",
+ start_date=datetime.datetime.now(),
+ )
+ other_project.full_clean()
+ other_project.save()
+ new_description = 'Some other description'
+ request = APIRequestFactory().post(
+ path=reverse('custom-report-detail', args=(self.report.pk,)),
+ data={
+ 'date': datetime.datetime.now().date(),
+ 'description': new_description,
+ 'project': other_project,
+ 'work_hours': Decimal('8.00'),
+ },
+ )
+ request.user = self.user
+ response = ReportDetail.as_view()(request, pk=self.report.pk)
+ self.report.refresh_from_db()
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.data['errors']['project'][0].code, 'does_not_exist')
+ self.assertNotEqual(new_description, self.report.description)
+ self.assertNotEqual(other_project, self.report.project)
+
+ def test_custom_report_detail_view_project_field_should_not_display_projects_the_author_is_not_a_member_of(self):
+ other_project = Project(
+ name="Other Project",
+ start_date=datetime.datetime.now(),
+ )
+ other_project.full_clean()
+ other_project.save()
+ request = APIRequestFactory().get(path=reverse('custom-report-detail', args=(self.report.pk,)))
+ request.user = self.user
+ response = ReportDetail.as_view()(request, pk=self.report.pk)
+ self.assertEqual(response.status_code, 200)
+ self.assertTrue(other_project not in response.data['serializer']._fields['project'].queryset)
+
class DeleteReportTests(TestCase):
def setUp(self):
diff --git a/employees/views.py b/employees/views.py
index 28e71640b..c8e87d09b 100644
--- a/employees/views.py
+++ b/employees/views.py
@@ -8,8 +8,10 @@
from employees.common.strings import ReportDetailStrings
from employees.common.strings import ReportListStrings
+from employees.forms import ProjectJoinForm
from employees.models import Report
from employees.serializers import ReportSerializer
+from managers.models import Project
class ReportViewSet(viewsets.ModelViewSet):
@@ -37,6 +39,8 @@ def query_as_dict(query_set):
class ReportList(APIView):
renderer_classes = [renderers.TemplateHTMLRenderer]
template_name = 'employees/report_list.html'
+ reports_dict = {}
+ project_form = ''
permission_classes = (
permissions.IsAuthenticated,
)
@@ -44,30 +48,67 @@ class ReportList(APIView):
def get_queryset(self):
return Report.objects.filter(author=self.request.user).order_by('-date', 'project__name')
- def get(self, request):
- reports_serializer = ReportSerializer(context={'request': request})
- reports_dict = query_as_dict(self.get_queryset())
+ def _add_project(self, serializer, project):
+ project.members.add(self.request.user)
+ project.full_clean()
+ project.save()
+ serializer.fields['project'].initial = project
+
+ def _create_serializer(self):
+ reports_serializer = ReportSerializer(context={'request': self.request}, )
+ reports_serializer.fields['project'].queryset = \
+ Project.objects.filter(
+ members__id=self.request.user.id
+ ).order_by('name')
+ return reports_serializer
+
+ def initial(self, request, *args, **kwargs):
+ super().initial(request, *args, **kwargs)
+ self.reports_dict = query_as_dict(self.get_queryset())
+ self.project_form = ProjectJoinForm(
+ queryset=Project.objects.exclude(members__id=self.request.user.id).order_by('name'),
+ )
+
+ def get(self, _request):
return Response({
- 'serializer': reports_serializer,
- 'reports_dict': reports_dict,
+ 'serializer': self._create_serializer(),
+ 'reports_dict': self.reports_dict,
'UI_text': ReportListStrings,
+ 'project_form': self.project_form,
})
def post(self, request):
reports_serializer = ReportSerializer(data=request.data, context={'request': request})
- if not reports_serializer.is_valid():
+ if 'join' in request.POST:
+ project_id = request.POST['projects']
+ project = Project.objects.get(id=int(project_id))
+ self._add_project(serializer=reports_serializer, project=project)
+ self.project_form = ProjectJoinForm(
+ queryset=Project.objects.exclude(members__id=self.request.user.id).order_by('name'),
+ )
+ reports_serializer = self._create_serializer()
+ reports_serializer.fields['project'].initial = project
+ return Response({
+ 'serializer': reports_serializer,
+ 'reports_dict': self.reports_dict,
+ 'UI_text': ReportListStrings,
+ 'project_form': self.project_form,
+ })
+
+ elif not reports_serializer.is_valid():
return Response({
'serializer': reports_serializer,
- 'reports_dict': query_as_dict(self.get_queryset()),
+ 'reports_dict': self.reports_dict,
'errors': reports_serializer.errors,
'UI_text': ReportListStrings,
+ 'project_form': self.project_form,
})
reports_serializer.save(author=self.request.user)
- reports_serializer = ReportSerializer(context={'request': request})
return Response({
- 'serializer': reports_serializer,
+ 'serializer': self._create_serializer(),
'reports_dict': query_as_dict(self.get_queryset()),
'UI_text': ReportListStrings,
+ 'project_form': self.project_form,
}, status=201)
@@ -78,9 +119,20 @@ class ReportDetail(APIView):
permissions.IsAuthenticated,
)
+ def _create_serializer(self, report, data=None):
+ if data is None:
+ reports_serializer = ReportSerializer(report, context={'request': self.request},)
+ else:
+ reports_serializer = ReportSerializer(report, data=data, context={'request': self.request}, )
+ reports_serializer.fields['project'].queryset = \
+ Project.objects.filter(
+ members__id=report.author.pk
+ ).order_by('name')
+ return reports_serializer
+
def get(self, request, pk):
report = get_object_or_404(Report, pk=pk)
- serializer = ReportSerializer(report, context={'request': request})
+ serializer = self._create_serializer(report)
return Response({
'serializer': serializer,
'report': report,
@@ -90,11 +142,7 @@ def get(self, request, pk):
def post(self, request, pk):
if "discard" not in request.POST:
report = get_object_or_404(Report, pk=pk)
- serializer = ReportSerializer(
- report,
- data=request.data,
- context={'request': request}
- )
+ serializer = self._create_serializer(report, request.data)
if not serializer.is_valid():
return Response({
'serializer': serializer,