From 0e6a906d6eb3d68437e8a71d4305976e8b84dc32 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 14:17:41 +0100
Subject: [PATCH 01/10] view_daily_planning permission implemented

---
 .../web/migrations/0141_auto_20200319_1301.py | 23 +++++++++++++++++++
 smash/web/models/appointment_type_link.py     |  4 ++++
 smash/web/templates/sidebar.html              | 14 ++++++-----
 smash/web/tests/view/test_daily_planning.py   | 20 ++++++++++++++++
 smash/web/views/daily_planning.py             |  9 +++++---
 5 files changed, 61 insertions(+), 9 deletions(-)
 create mode 100644 smash/web/migrations/0141_auto_20200319_1301.py
 create mode 100644 smash/web/tests/view/test_daily_planning.py

diff --git a/smash/web/migrations/0141_auto_20200319_1301.py b/smash/web/migrations/0141_auto_20200319_1301.py
new file mode 100644
index 00000000..82d45afe
--- /dev/null
+++ b/smash/web/migrations/0141_auto_20200319_1301.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0140_auto_20190528_0953'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointmenttypelink',
+            options={'permissions': [('view_daily_planning', 'Can see daily planning')]},
+        ),
+        migrations.AlterField(
+            model_name='appointmenttype',
+            name='calendar_font_color',
+            field=models.CharField(default=b'#00000', max_length=2000, verbose_name=b'Calendar font color'),
+        ),
+    ]
diff --git a/smash/web/models/appointment_type_link.py b/smash/web/models/appointment_type_link.py
index a48a5502..c2de6050 100644
--- a/smash/web/models/appointment_type_link.py
+++ b/smash/web/models/appointment_type_link.py
@@ -2,6 +2,10 @@ from django.db import models
 
 
 class AppointmentTypeLink(models.Model):
+    class Meta:
+        permissions = [
+            ("view_daily_planning", "Can see daily planning"),
+        ]
     appointment = models.ForeignKey("web.Appointment", on_delete=models.CASCADE)
     appointment_type = models.ForeignKey("web.AppointmentType", on_delete=models.CASCADE)
     date_when = models.DateTimeField(null=True, default=None)
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index a91be3d7..c33796da 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -16,12 +16,14 @@
         </a>
     </li>
 
-    <li data-desc="daily_planning">
-        <a href="{% url 'web.views.daily_planning' %}">
-            <i class="fa fa-clock-o"></i>
-            <span>Daily Planning</span>
-        </a>
-    </li>
+    {% if "view_daily_planning" in permissions %}
+        <li data-desc="daily_planning">
+            <a href="{% url 'web.views.daily_planning' %}">
+                <i class="fa fa-clock-o"></i>
+                <span>Daily Planning</span>
+            </a>
+        </li>
+    {% endif %}
 
     {% if "change_worker" in permissions %}
     <li data-desc="workers">
diff --git a/smash/web/tests/view/test_daily_planning.py b/smash/web/tests/view/test_daily_planning.py
new file mode 100644
index 00000000..096cc920
--- /dev/null
+++ b/smash/web/tests/view/test_daily_planning.py
@@ -0,0 +1,20 @@
+import logging
+
+from django.urls import reverse
+
+from web.tests import LoggedInTestCase
+
+logger = logging.getLogger(__name__)
+
+
+class DailyPlanningViewTests(LoggedInTestCase):
+    def test_visit_details_request(self):
+        self.login_as_admin()
+        response = self.client.get(reverse('web.views.daily_planning'))
+
+        self.assertEqual(response.status_code, 200)
+
+    def test_visit_details_request_without_permissions(self):
+        self.login_as_staff()
+        response = self.client.get(reverse('web.views.daily_planning'))
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/views/daily_planning.py b/smash/web/views/daily_planning.py
index 40ab776c..d720816f 100644
--- a/smash/web/views/daily_planning.py
+++ b/smash/web/views/daily_planning.py
@@ -1,12 +1,15 @@
 # coding=utf-8
-import logging
 
 from django.views.generic import TemplateView
-from . import wrap_response
+
+from web.decorators import PermissionDecorator
 from web.models.worker_study_role import STUDY_ROLE_CHOICES
+from . import wrap_response
+
 
 class TemplateDailyPlannerView(TemplateView):
+    @PermissionDecorator('view_daily_planning', 'daily_planning')
     def get(self, request, *args, **kwargs):
         context = self.get_context_data(**kwargs)
         context['worker_study_roles'] = STUDY_ROLE_CHOICES
-        return wrap_response(request, 'daily_planning.html', context)
\ No newline at end of file
+        return wrap_response(request, 'daily_planning.html', context)
-- 
GitLab


From 2a5a47c269ed36c884611b97beb25168afa41737 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 14:38:41 +0100
Subject: [PATCH 02/10] edit equipement permission added

---
 smash/web/templates/sidebar.html        |  2 ++
 smash/web/tests/view/test_equipments.py | 12 ++++++++++++
 smash/web/views/equipment.py            |  7 ++++++-
 3 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index c33796da..f623b037 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -42,7 +42,9 @@
             </span>
         </a>
         <ul class="treeview-menu">
+            {% if "change_item" in permissions %}
                 <li data-desc="equipment_items"><a href="{% url 'web.views.equipment' %}">Equipment items</a></li>
+            {% endif %}
             {% if "change_appointmenttype" in permissions %}
                 <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment Types</a></li>
             {% endif %}
diff --git a/smash/web/tests/view/test_equipments.py b/smash/web/tests/view/test_equipments.py
index 99e312a8..34cba24a 100644
--- a/smash/web/tests/view/test_equipments.py
+++ b/smash/web/tests/view/test_equipments.py
@@ -10,7 +10,14 @@ logger = logging.getLogger(__name__)
 
 
 class EquipmentTests(LoggedInTestCase):
+
+    def test_list_without_permissions(self):
+        self.login_as_staff()
+        response = self.client.get(reverse('web.views.equipment'))
+        self.assertEqual(response.status_code, 302)
+
     def test_equipment_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment',
             'web.views.equipment_add',
@@ -21,6 +28,7 @@ class EquipmentTests(LoggedInTestCase):
             self.assertEqual(response.status_code, 200)
 
     def test_equipment_edit_request(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_edit',
                        kwargs={'equipment_id': str(item.id)})
@@ -28,6 +36,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_equipment_delete_request(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_delete',
                        kwargs={'equipment_id': str(item.id)})
@@ -35,6 +44,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
 
     def test_equipment_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_add')
         data = {
             'name': 'The mysterious potion',
@@ -48,6 +58,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_equipment_edit(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_edit',
                        kwargs={'equipment_id': str(item.id)})
@@ -64,6 +75,7 @@ class EquipmentTests(LoggedInTestCase):
             self.assertEqual(getattr(freshly_edited, key, ''), data[key])
 
     def test_equipment_delete(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_delete',
                        kwargs={'equipment_id': str(item.id)})
diff --git a/smash/web/views/equipment.py b/smash/web/views/equipment.py
index ff498134..a4c36568 100644
--- a/smash/web/views/equipment.py
+++ b/smash/web/views/equipment.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
-from ..models import Item
 from ..forms.forms import ItemForm
+from ..models import Item
 
 
+@PermissionDecorator('change_item', 'item')
 def equipment(request):
     equipment_list = Item.objects.order_by('-name')
     context = {
@@ -15,6 +17,7 @@ def equipment(request):
     return wrap_response(request, "equipment_and_rooms/equipment/index.html", context)
 
 
+@PermissionDecorator('change_item', 'item')
 def equipment_add(request):
     if request.method == 'POST':
         form = ItemForm(request.POST)
@@ -27,6 +30,7 @@ def equipment_add(request):
     return wrap_response(request, 'equipment_and_rooms/equipment/add.html', {'form': form})
 
 
+@PermissionDecorator('change_item', 'item')
 def equipment_edit(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     if request.method == 'POST':
@@ -40,6 +44,7 @@ def equipment_edit(request, equipment_id):
     return wrap_response(request, 'equipment_and_rooms/equipment/edit.html', {'form': form})
 
 
+@PermissionDecorator('change_item', 'item')
 def equipment_delete(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     the_item.delete()
-- 
GitLab


From 2972e59b8808655b42f9225a72dfbbb906addfa9 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 14:58:31 +0100
Subject: [PATCH 03/10] modif_flyingteam permission implemented

---
 smash/web/templates/sidebar.html          |  2 ++
 smash/web/tests/view/test_flying_teams.py | 14 ++++++++++++++
 smash/web/views/flying_teams.py           |  7 ++++++-
 3 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index f623b037..e96f912d 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -48,7 +48,9 @@
             {% if "change_appointmenttype" in permissions %}
                 <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment Types</a></li>
             {% endif %}
+            {% if "change_flyingteam" in permissions %}
                 <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying teams</a></li>
+            {% endif %}
                 <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
                 <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
         </ul>
diff --git a/smash/web/tests/view/test_flying_teams.py b/smash/web/tests/view/test_flying_teams.py
index 32cfe22f..e282c3e9 100644
--- a/smash/web/tests/view/test_flying_teams.py
+++ b/smash/web/tests/view/test_flying_teams.py
@@ -17,6 +17,7 @@ class FlyingTeamTests(LoggedInTestCase):
         return 'Random' + ''.join(random.choice(letters) for x in range(15))
 
     def test_flying_team_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment_and_rooms.flying_teams',
             'web.views.equipment_and_rooms.flying_teams_add',
@@ -26,7 +27,18 @@ class FlyingTeamTests(LoggedInTestCase):
             response = self.client.get(reverse(page))
             self.assertEqual(response.status_code, 200)
 
+    def test_flying_team_requests_without_permission(self):
+        pages = [
+            'web.views.equipment_and_rooms.flying_teams',
+            'web.views.equipment_and_rooms.flying_teams_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(reverse(page))
+            self.assertEqual(response.status_code, 302)
+
     def test_flying_team_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_and_rooms.flying_teams_add')
         data = {
             'place': self.generate_more_or_less_random_name()
@@ -38,6 +50,7 @@ class FlyingTeamTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_flying_team_edit(self):
+        self.login_as_admin()
         flying_team = create_flying_team()
         page = reverse('web.views.equipment_and_rooms.flying_teams_edit',
                        kwargs={'flying_team_id': str(flying_team.id)})
@@ -51,6 +64,7 @@ class FlyingTeamTests(LoggedInTestCase):
         self.assertEqual(freshly_edited.place, data["place"])
 
     def test_flying_team_edit_request(self):
+        self.login_as_admin()
         flying_team = create_flying_team()
         page = reverse('web.views.equipment_and_rooms.flying_teams_edit',
                        kwargs={'flying_team_id': str(flying_team.id)})
diff --git a/smash/web/views/flying_teams.py b/smash/web/views/flying_teams.py
index dc6c4752..f4e3cb78 100644
--- a/smash/web/views/flying_teams.py
+++ b/smash/web/views/flying_teams.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
-from ..models import FlyingTeam
 from ..forms.forms import FlyingTeamAddForm, FlyingTeamEditForm
+from ..models import FlyingTeam
 
 
+@PermissionDecorator('change_flyingteam', 'item')
 def flying_teams(request):
     flying_team_list = FlyingTeam.objects.order_by('-place')
     context = {
@@ -16,6 +18,8 @@ def flying_teams(request):
                          "equipment_and_rooms/flying_teams/index.html",
                          context)
 
+
+@PermissionDecorator('change_flyingteam', 'item')
 def flying_teams_add(request):
     if request.method == 'POST':
         form = FlyingTeamAddForm(request.POST)
@@ -28,6 +32,7 @@ def flying_teams_add(request):
     return wrap_response(request, 'equipment_and_rooms/flying_teams/add.html', {'form': form})
 
 
+@PermissionDecorator('change_flyingteam', 'item')
 def flying_teams_edit(request, flying_team_id):
     the_flying_team = get_object_or_404(FlyingTeam, id=flying_team_id)
     if request.method == 'POST':
-- 
GitLab


From f9c713c1fe0bd239d493466b495d592150667d3b Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 15:06:53 +0100
Subject: [PATCH 04/10] modify_room permission implemented

---
 smash/web/templates/sidebar.html   |  2 ++
 smash/web/tests/view/test_rooms.py | 20 ++++++++++++++++++--
 smash/web/views/flying_teams.py    |  6 +++---
 smash/web/views/rooms.py           |  5 +++++
 4 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index e96f912d..e465b4af 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -52,7 +52,9 @@
                 <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying teams</a></li>
             {% endif %}
                 <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
+            {% if "change_room" in permissions %}
                 <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
+            {% endif %}
         </ul>
     </li>
 
diff --git a/smash/web/tests/view/test_rooms.py b/smash/web/tests/view/test_rooms.py
index ecf11616..b607e810 100644
--- a/smash/web/tests/view/test_rooms.py
+++ b/smash/web/tests/view/test_rooms.py
@@ -2,15 +2,16 @@ import logging
 
 from django.urls import reverse
 
-from web.tests.functions import create_room, create_item
-from web.models import Item, Room
+from web.models import Room
 from web.tests import LoggedInTestCase
+from web.tests.functions import create_room, create_item
 
 logger = logging.getLogger(__name__)
 
 
 class RoomsTests(LoggedInTestCase):
     def test_rooms_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment_and_rooms.rooms',
             'web.views.equipment_and_rooms.rooms_add',
@@ -20,7 +21,18 @@ class RoomsTests(LoggedInTestCase):
             response = self.client.get(reverse(page))
             self.assertEqual(response.status_code, 200)
 
+    def test_rooms_requests_without_permission(self):
+        pages = [
+            'web.views.equipment_and_rooms.rooms',
+            'web.views.equipment_and_rooms.rooms_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(reverse(page))
+            self.assertEqual(response.status_code, 302)
+
     def test_rooms_edit_request(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_edit',
                        kwargs={'room_id': str(room.id)})
@@ -28,6 +40,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_rooms_delete_request(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_delete',
                        kwargs={'room_id': str(room.id)})
@@ -35,6 +48,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
 
     def test_rooms_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_and_rooms.rooms_add')
         item = create_item()
         data = {
@@ -53,6 +67,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_rooms_edit(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_edit',
                        kwargs={'room_id': str(room.id)})
@@ -72,6 +87,7 @@ class RoomsTests(LoggedInTestCase):
             self.assertEqual(getattr(freshly_edited, key, ''), data[key])
 
     def test_rooms_delete(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_delete',
                        kwargs={'room_id': str(room.id)})
diff --git a/smash/web/views/flying_teams.py b/smash/web/views/flying_teams.py
index f4e3cb78..e96fe301 100644
--- a/smash/web/views/flying_teams.py
+++ b/smash/web/views/flying_teams.py
@@ -7,7 +7,7 @@ from ..forms.forms import FlyingTeamAddForm, FlyingTeamEditForm
 from ..models import FlyingTeam
 
 
-@PermissionDecorator('change_flyingteam', 'item')
+@PermissionDecorator('change_flyingteam', 'flyingteam')
 def flying_teams(request):
     flying_team_list = FlyingTeam.objects.order_by('-place')
     context = {
@@ -19,7 +19,7 @@ def flying_teams(request):
                          context)
 
 
-@PermissionDecorator('change_flyingteam', 'item')
+@PermissionDecorator('change_flyingteam', 'flyingteam')
 def flying_teams_add(request):
     if request.method == 'POST':
         form = FlyingTeamAddForm(request.POST)
@@ -32,7 +32,7 @@ def flying_teams_add(request):
     return wrap_response(request, 'equipment_and_rooms/flying_teams/add.html', {'form': form})
 
 
-@PermissionDecorator('change_flyingteam', 'item')
+@PermissionDecorator('change_flyingteam', 'flyingteam')
 def flying_teams_edit(request, flying_team_id):
     the_flying_team = get_object_or_404(FlyingTeam, id=flying_team_id)
     if request.method == 'POST':
diff --git a/smash/web/views/rooms.py b/smash/web/views/rooms.py
index 2ff626e9..5c20463b 100644
--- a/smash/web/views/rooms.py
+++ b/smash/web/views/rooms.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms.forms import RoomForm
 from ..models import Room
 
 
+@PermissionDecorator('change_room', 'room')
 def rooms(request):
     rooms_list = Room.objects.order_by('-city')
     context = {
@@ -17,6 +19,7 @@ def rooms(request):
                          context)
 
 
+@PermissionDecorator('change_room', 'room')
 def rooms_add(request):
     if request.method == 'POST':
         form = RoomForm(request.POST)
@@ -29,6 +32,7 @@ def rooms_add(request):
     return wrap_response(request, 'equipment_and_rooms/rooms/add.html', {'form': form})
 
 
+@PermissionDecorator('change_room', 'room')
 def rooms_edit(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     if request.method == 'POST':
@@ -42,6 +46,7 @@ def rooms_edit(request, room_id):
     return wrap_response(request, 'equipment_and_rooms/rooms/edit.html', {'form': form})
 
 
+@PermissionDecorator('change_room', 'room')
 def rooms_delete(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     the_room.delete()
-- 
GitLab


From 290e82b7e37ba68d32478f3ec827ce0b55561ea7 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 15:35:14 +0100
Subject: [PATCH 05/10] permission to send sample list email

---
 .../web/migrations/0142_auto_20200319_1415.py | 19 ++++++++
 smash/web/models/appointment.py               |  3 ++
 smash/web/templates/sidebar.html              | 48 +++++++++++--------
 smash/web/tests/view/test_kit_request.py      | 15 +++++-
 smash/web/views/kit.py                        | 12 +++--
 5 files changed, 71 insertions(+), 26 deletions(-)
 create mode 100644 smash/web/migrations/0142_auto_20200319_1415.py

diff --git a/smash/web/migrations/0142_auto_20200319_1415.py b/smash/web/migrations/0142_auto_20200319_1415.py
new file mode 100644
index 00000000..ea5d4620
--- /dev/null
+++ b/smash/web/migrations/0142_auto_20200319_1415.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 14:15
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0141_auto_20200319_1301'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointment',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list')]},
+        ),
+    ]
diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py
index 9b5b3944..ad4e9ff6 100644
--- a/smash/web/models/appointment.py
+++ b/smash/web/models/appointment.py
@@ -11,6 +11,9 @@ from . import ConfigurationItem
 class Appointment(models.Model):
     class Meta:
         app_label = 'web'
+        permissions = [
+            ("send_sample_mail_for_appointments", "Can send sample collection list"),
+        ]
 
     APPOINTMENT_STATUS_SCHEDULED = 'SCHEDULED'
     APPOINTMENT_STATUS_FINISHED = 'FINISHED'
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index e465b4af..7857b135 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -34,29 +34,35 @@
     </li>
     {% endif %}
 
-    <li data-desc="equipment_and_rooms" class="treeview">
-        <a href="{% url 'web.views.equipment_and_rooms' %}">
-            <i class="fa fa-building-o"></i> <span>Equipment &amp; Rooms</span>
-            <span class="pull-right-container">
+    {% if "change_item" in permissions or "change_appointmenttype" in permissions or "change_appointmenttype" in permissions or "change_flyingteam" in permissions or "send_sample_mail_for_appointments" in permissions or "change_room" in permissions %}
+        <li data-desc="equipment_and_rooms" class="treeview">
+            <a href="{% url 'web.views.equipment_and_rooms' %}">
+                <i class="fa fa-building-o"></i> <span>Equipment &amp; Rooms</span>
+                <span class="pull-right-container">
               <i class="fa fa-angle-left pull-right"></i>
             </span>
-        </a>
-        <ul class="treeview-menu">
-            {% if "change_item" in permissions %}
-                <li data-desc="equipment_items"><a href="{% url 'web.views.equipment' %}">Equipment items</a></li>
-            {% endif %}
-            {% if "change_appointmenttype" in permissions %}
-                <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment Types</a></li>
-            {% endif %}
-            {% if "change_flyingteam" in permissions %}
-                <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying teams</a></li>
-            {% endif %}
-                <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
-            {% if "change_room" in permissions %}
-                <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
-            {% endif %}
-        </ul>
-    </li>
+            </a>
+            <ul class="treeview-menu">
+                {% if "change_item" in permissions %}
+                    <li data-desc="equipment_items"><a href="{% url 'web.views.equipment' %}">Equipment items</a></li>
+                {% endif %}
+                {% if "change_appointmenttype" in permissions %}
+                    <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment
+                        Types</a></li>
+                {% endif %}
+                {% if "change_flyingteam" in permissions %}
+                    <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying
+                        teams</a></li>
+                {% endif %}
+                {% if "send_sample_mail_for_appointments" in permissions %}
+                    <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
+                {% endif %}
+                {% if "change_room" in permissions %}
+                    <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
+                {% endif %}
+            </ul>
+        </li>
+    {% endif %}
 
     <li data-desc="statistics">
         <a href="{% url 'web.views.statistics' %}">
diff --git a/smash/web/tests/view/test_kit_request.py b/smash/web/tests/view/test_kit_request.py
index 387001f3..72222f5d 100644
--- a/smash/web/tests/view/test_kit_request.py
+++ b/smash/web/tests/view/test_kit_request.py
@@ -5,7 +5,8 @@ from django.urls import reverse
 
 from web.models import Item, Appointment, AppointmentTypeLink
 from web.tests import LoggedInTestCase
-from web.tests.functions import create_appointment_type, create_appointment, create_visit, create_appointment_without_visit
+from web.tests.functions import create_appointment_type, create_appointment, create_visit, \
+    create_appointment_without_visit
 from web.views.kit import get_kit_requests
 from web.views.notifications import get_today_midnight_date
 
@@ -13,10 +14,16 @@ from web.views.notifications import get_today_midnight_date
 class ViewFunctionsTests(LoggedInTestCase):
 
     def test_kit_requests(self):
+        self.login_as_admin()
         response = self.client.get(reverse('web.views.kit_requests'))
         self.assertEqual(response.status_code, 200)
 
+    def test_kit_requests_without_permission(self):
+        response = self.client.get(reverse('web.views.kit_requests'))
+        self.assertEqual(response.status_code, 302)
+
     def test_kit_requests_2(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -35,6 +42,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertTrue(item_name in response.content)
 
     def test_kit_requests_4(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -54,6 +62,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertFalse(item_name in response.content)
 
     def test_kit_requests_3(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -72,6 +81,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertTrue(item_name in response.content)
 
     def test_kit_requests_order(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -104,6 +114,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(appointment2, result['appointments'][2])
 
     def test_kit_requests_for_appointment_with_two_types(self):
+        self.login_as_admin()
         item = Item.objects.create(disposable=True, name="item 1")
         appointment_type = create_appointment_type()
         appointment_type.required_equipment.add(item)
@@ -129,6 +140,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(1, len(result["appointments"]))
 
     def test_kit_requests_send_email(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -150,6 +162,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(1, len(mail.outbox))
 
     def test_kit_request_send_mail_with_general_appointment(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
diff --git a/smash/web/views/kit.py b/smash/web/views/kit.py
index 79c42ef5..159338fa 100644
--- a/smash/web/views/kit.py
+++ b/smash/web/views/kit.py
@@ -13,6 +13,7 @@ from django_cron import CronJobBase, Schedule
 from django_cron.models import CronJobLog
 
 from notifications import get_filter_locations, get_today_midnight_date
+from web.decorators import PermissionDecorator
 from web.models import ConfigurationItem, Language, Worker
 from web.models.constants import KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
     KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT
@@ -60,6 +61,7 @@ def get_kit_requests(user, start_date=None, end_date=None):
     return result
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
 def get_kit_requests_data(request, start_date=None, end_date=None):
     form = KitRequestForm()
     if request.method == 'POST':
@@ -76,6 +78,7 @@ def get_kit_requests_data(request, start_date=None, end_date=None):
     return params
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
 def kit_requests(request):
     return wrap_response(request, 'equipment_and_rooms/kit_requests/kit_requests.html', get_kit_requests_data(request))
 
@@ -85,7 +88,7 @@ def send_mail(data):
     if data["end_date"] is not None:
         end_date_str = data["end_date"].strftime('%Y-%m-%d')
     title = "Samples between " + \
-        data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
+            data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
 
     cell_style = "padding: 8px; line-height: 1.42857143; vertical-align: top; " \
                  "font-size: 14px; font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;"
@@ -109,10 +112,10 @@ def send_mail(data):
             row_style = ' background-color: #f9f9f9;'
         email_body += "<tr style='" + row_style + "'>"
         email_body += "<td style='" + cell_style + "'>" + \
-            appointment.datetime_when.strftime('%Y-%m-%d %H:%M') + "</td>"
+                      appointment.datetime_when.strftime('%Y-%m-%d %H:%M') + "</td>"
         if appointment.visit is not None and appointment.visit.subject is not None:
             email_body += "<td style='" + cell_style + "'>" + \
-                appointment.visit.subject.nd_number + "</td>"
+                          appointment.visit.subject.nd_number + "</td>"
         else:
             email_body += "<td style='" + cell_style + "'>" + '-' + "</td>"
         email_body += "<td style='" + cell_style + "'>"
@@ -126,7 +129,7 @@ def send_mail(data):
             location += " (" + unicode(appointment.flying_team) + ")"
         email_body += "<td style='" + cell_style + "'>" + location + "</td>"
         email_body += "<td style='" + cell_style + "'>" + \
-            unicode(appointment.worker_assigned) + "</td>"
+                      unicode(appointment.worker_assigned) + "</td>"
         email_body += "</tr>"
     email_body += "</tbody></table>"
     recipients = ConfigurationItem.objects.get(
@@ -136,6 +139,7 @@ def send_mail(data):
     EmailSender().send_email(title, email_body, recipients, cc_recipients)
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
 def kit_requests_send_mail(request, start_date, end_date=None):
     data = get_kit_requests_data(request, start_date, end_date)
     try:
-- 
GitLab


From 896bcfc96ea091ca79a89b1ffc0acbb36130c3f2 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 15:53:57 +0100
Subject: [PATCH 06/10] view_statistics permission implemented

---
 .../web/migrations/0143_auto_20200319_1446.py | 19 +++++++++++++++++++
 smash/web/models/appointment.py               |  1 +
 smash/web/templates/sidebar.html              | 14 ++++++++------
 smash/web/tests/view/test_statistics.py       |  6 ++++++
 smash/web/views/statistics.py                 |  2 ++
 5 files changed, 36 insertions(+), 6 deletions(-)
 create mode 100644 smash/web/migrations/0143_auto_20200319_1446.py

diff --git a/smash/web/migrations/0143_auto_20200319_1446.py b/smash/web/migrations/0143_auto_20200319_1446.py
new file mode 100644
index 00000000..d6c74b0c
--- /dev/null
+++ b/smash/web/migrations/0143_auto_20200319_1446.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 14:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0142_auto_20200319_1415'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointment',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list'), ('view_statistics', 'Can see statistics')]},
+        ),
+    ]
diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py
index ad4e9ff6..c7a52d41 100644
--- a/smash/web/models/appointment.py
+++ b/smash/web/models/appointment.py
@@ -13,6 +13,7 @@ class Appointment(models.Model):
         app_label = 'web'
         permissions = [
             ("send_sample_mail_for_appointments", "Can send sample collection list"),
+            ("view_statistics", "Can see statistics"),
         ]
 
     APPOINTMENT_STATUS_SCHEDULED = 'SCHEDULED'
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index 7857b135..00aa8ca8 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -64,12 +64,14 @@
         </li>
     {% endif %}
 
-    <li data-desc="statistics">
-        <a href="{% url 'web.views.statistics' %}">
-            <i class="fa fa-bar-chart" aria-hidden="true"></i>
-            <span>Statistics</span>
-        </a>
-    </li>
+    {% if  "view_statistics" in permissions %}
+        <li data-desc="statistics">
+            <a href="{% url 'web.views.statistics' %}">
+                <i class="fa fa-bar-chart" aria-hidden="true"></i>
+                <span>Statistics</span>
+            </a>
+        </li>
+    {% endif %}
 
     <li data-desc="mail_templates">
         <a href="{% url 'web.views.mail_templates' %}">
diff --git a/smash/web/tests/view/test_statistics.py b/smash/web/tests/view/test_statistics.py
index 73737107..aa7fee20 100644
--- a/smash/web/tests/view/test_statistics.py
+++ b/smash/web/tests/view/test_statistics.py
@@ -10,6 +10,7 @@ __author__ = 'Valentin Grouès'
 
 class TestStatisticsView(LoggedInTestCase):
     def test_statistics_request(self):
+        self.login_as_admin()
         url = reverse('web.views.statistics')
         response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
@@ -19,3 +20,8 @@ class TestStatisticsView(LoggedInTestCase):
         response = self.client.get(url, {"month": 10, "year": 2017, "subject_type": -1, "visit": -1})
         content = response.content
         self.assertIn('<option value="10" selected>October', content)
+
+    def test_statistics_request_without_permission(self):
+        url = reverse('web.views.statistics')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/views/statistics.py b/smash/web/views/statistics.py
index 67bdb79c..b8cd84cc 100644
--- a/smash/web/views/statistics.py
+++ b/smash/web/views/statistics.py
@@ -1,9 +1,11 @@
 # coding=utf-8
+from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms import StatisticsForm
 from ..statistics import StatisticsManager, get_previous_year_and_month
 
 
+@PermissionDecorator('view_statistics', 'appointment')
 def statistics(request):
     statistics_manager = StatisticsManager()
     visit_choices = [("-1", "all")]
-- 
GitLab


From 916865ab956731c59cb177d4a27e0666bfec3738 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 16:13:03 +0100
Subject: [PATCH 07/10] modify_mailtemplate permission implemented

---
 smash/web/templates/sidebar.html  | 15 +++++++++------
 smash/web/tests/view/test_mail.py | 11 ++++++++++-
 smash/web/views/mails.py          | 10 +++++++++-
 3 files changed, 28 insertions(+), 8 deletions(-)

diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index 00aa8ca8..5aceea8b 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -73,12 +73,15 @@
         </li>
     {% endif %}
 
-    <li data-desc="mail_templates">
-        <a href="{% url 'web.views.mail_templates' %}">
-            <i class="fa fa-envelope-o"></i>
-            <span>Mail templates</span>
-        </a>
-    </li>
+
+    {% if  "change_mailtemplate" in permissions %}
+        <li data-desc="mail_templates">
+            <a href="{% url 'web.views.mail_templates' %}">
+                <i class="fa fa-envelope-o"></i>
+                <span>Mail templates</span>
+            </a>
+        </li>
+    {% endif %}
 
     <li data-desc="export">
         <a href="{% url 'web.views.export' %}">
diff --git a/smash/web/tests/view/test_mail.py b/smash/web/tests/view/test_mail.py
index 900c84a7..1f11340a 100644
--- a/smash/web/tests/view/test_mail.py
+++ b/smash/web/tests/view/test_mail.py
@@ -4,8 +4,8 @@ from django.urls import reverse
 
 from web.models import MailTemplate
 from web.models.constants import MAIL_TEMPLATE_CONTEXT_VOUCHER
-from web.tests.functions import create_voucher, get_resource_path
 from web.tests import LoggedInTestCase
+from web.tests.functions import create_voucher, get_resource_path
 
 logger = logging.getLogger(__name__)
 
@@ -20,3 +20,12 @@ class MailTests(LoggedInTestCase):
         page = reverse('web.views.mail_template_generate_for_vouchers') + "?voucher_id=" + str(voucher.id)
         response = self.client.get(page)
         self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates(self):
+        self.login_as_admin()
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates_without_permission(self):
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/views/mails.py b/smash/web/views/mails.py
index ef4cf0b3..7b49409c 100644
--- a/smash/web/views/mails.py
+++ b/smash/web/views/mails.py
@@ -9,6 +9,7 @@ from django.urls import reverse_lazy
 from django.views.generic import DeleteView
 from django.views.generic import ListView
 
+from web.decorators import PermissionDecorator
 from web.docx_helper import merge_files
 from . import WrappedView
 from . import wrap_response
@@ -32,7 +33,11 @@ class MailTemplatesListView(ListView, WrappedView):
     context_object_name = "mail_templates"
     template_name = 'mail_templates/list.html'
 
-    def get_context_data(self, **kwargs):
+    @PermissionDecorator('change_mailtemplate', 'mailtemplate')
+    def dispatch(self, *args, **kwargs):
+        return super(MailTemplatesListView, self).dispatch(*args, **kwargs)
+
+    def get_context_data(self, *args, **kwargs):
         context = super(MailTemplatesListView, self).get_context_data()
         context['explanations'] = {"generic": MailTemplate.MAILS_TEMPLATE_GENERIC_TAGS,
                                    "subject": MailTemplate.MAILS_TEMPLATE_SUBJECT_TAGS,
@@ -43,6 +48,7 @@ class MailTemplatesListView(ListView, WrappedView):
         return context
 
 
+@PermissionDecorator('change_mailtemplate', 'mailtemplate')
 def mail_template_add(request):
     if request.method == 'POST':
         form = MailTemplateForm(request.POST, request.FILES)
@@ -59,6 +65,7 @@ def mail_template_add(request):
     return wrap_response(request, 'mail_templates/add.html', {'form': form})
 
 
+@PermissionDecorator('change_mailtemplate', 'mailtemplate')
 def mail_template_edit(request, pk):
     template = get_object_or_404(MailTemplate, pk=pk)
     if request.method == 'POST':
@@ -82,6 +89,7 @@ class MailTemplatesDeleteView(DeleteView, WrappedView):
     success_url = reverse_lazy('web.views.mail_templates')
     template_name = 'mail_templates/confirm_delete.html'
 
+    @PermissionDecorator('change_mailtemplate', 'mailtemplate')
     def delete(self, request, *args, **kwargs):
         messages.success(request, "Template deleted")
         try:
-- 
GitLab


From cd02637c7b89a4fbfdf90ae35cb81d04d7b82dab Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 16:29:07 +0100
Subject: [PATCH 08/10] export_subjects permission implemented

---
 .../web/migrations/0144_auto_20200319_1518.py | 19 +++++++
 smash/web/models/subject.py                   | 19 ++++---
 smash/web/templates/sidebar.html              | 14 +++---
 smash/web/tests/view/test_export.py           | 21 ++++++++
 smash/web/views/export.py                     | 50 +++++++++++--------
 5 files changed, 90 insertions(+), 33 deletions(-)
 create mode 100644 smash/web/migrations/0144_auto_20200319_1518.py

diff --git a/smash/web/migrations/0144_auto_20200319_1518.py b/smash/web/migrations/0144_auto_20200319_1518.py
new file mode 100644
index 00000000..bdb5b3ee
--- /dev/null
+++ b/smash/web/migrations/0144_auto_20200319_1518.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 15:18
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0143_auto_20200319_1446'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='subject',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list'), ('export_subjects', 'Can export subject data to excel/csv')]},
+        ),
+    ]
diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py
index b762bfe3..0d0eb57f 100644
--- a/smash/web/models/subject.py
+++ b/smash/web/models/subject.py
@@ -1,19 +1,24 @@
 # coding=utf-8
 import logging
+
 from django.db import models
+from django.db.models.signals import post_save
+from django.dispatch import receiver
 
 from constants import SEX_CHOICES, COUNTRY_OTHER_ID
 from web.models import Country, Visit, Appointment
 from . import Language
-from django.db.models.signals import post_save
-from django.dispatch import receiver
 
 logger = logging.getLogger(__name__)
 
-class Subject(models.Model):
 
+class Subject(models.Model):
     class Meta:
         app_label = 'web'
+        permissions = [
+            ("send_sample_mail_for_appointments", "Can send sample collection list"),
+            ("export_subjects", "Can export subject data to excel/csv"),
+        ]
 
     sex = models.CharField(max_length=1,
                            choices=SEX_CHOICES,
@@ -103,7 +108,7 @@ class Subject(models.Model):
     )
 
     def pretty_address(self):
-      return u'{} ({}), {}. {}'.format(self.address, self.postal_code, self.city, self.country)
+        return u'{} ({}), {}. {}'.format(self.address, self.postal_code, self.city, self.country)
 
     def mark_as_dead(self):
         self.dead = True
@@ -130,8 +135,8 @@ class Subject(models.Model):
         return "%s %s" % (self.first_name, self.last_name)
 
 
-#SIGNALS
+# SIGNALS
 @receiver(post_save, sender=Subject)
 def set_as_deceased(sender, instance, **kwargs):
-  if instance.dead:
-    instance.mark_as_dead()
\ No newline at end of file
+    if instance.dead:
+        instance.mark_as_dead()
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index 5aceea8b..9d215925 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -83,12 +83,14 @@
         </li>
     {% endif %}
 
-    <li data-desc="export">
-        <a href="{% url 'web.views.export' %}">
-            <i class="fa fa-file-excel-o"></i>
-            <span>Export</span>
-        </a>
-    </li>
+    {% if  "export_subjects" in permissions %}
+        <li data-desc="export">
+            <a href="{% url 'web.views.export' %}">
+                <i class="fa fa-file-excel-o"></i>
+                <span>Export</span>
+            </a>
+        </li>
+    {% endif %}
 
     {% if study.has_vouchers and "change_voucher" in permissions%}
     <li data-desc="vouchers">
diff --git a/smash/web/tests/view/test_export.py b/smash/web/tests/view/test_export.py
index d6938476..3f6a5c99 100644
--- a/smash/web/tests/view/test_export.py
+++ b/smash/web/tests/view/test_export.py
@@ -9,26 +9,47 @@ from web.views.export import subject_to_row_for_fields, DROP_OUT_FIELD
 
 class TestExportView(LoggedInTestCase):
     def test_export_subjects_to_csv(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "subjects"}))
         self.assertEqual(response.status_code, 200)
 
+    def test_export_subjects_to_csv_without_permission(self):
+        response = self.client.get(reverse("web.views.mail_templates"))
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "subjects"}))
+        self.assertEqual(response.status_code, 302)
+
     def test_render_export(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export'))
         self.assertEqual(response.status_code, 200)
 
+    def test_render_export_without_permission(self):
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export'))
+        self.assertEqual(response.status_code, 302)
+
     def test_export_appointments_to_csv(self):
+        self.login_as_admin()
         create_appointment()
         response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "appointments"}))
         self.assertEqual(response.status_code, 200)
 
     def test_export_subjects_to_excel(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export_to_excel', kwargs={'data_type': "subjects"}))
         self.assertEqual(response.status_code, 200)
 
+    def test_export_subjects_to_excel_without_permission(self):
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export_to_excel', kwargs={'data_type': "subjects"}))
+        self.assertEqual(response.status_code, 302)
+
     def test_export_appointments_to_excel(self):
+        self.login_as_admin()
         appointment = create_appointment()
         appointment.visit = None
         appointment.save()
diff --git a/smash/web/views/export.py b/smash/web/views/export.py
index 02fc1a82..1cb62712 100644
--- a/smash/web/views/export.py
+++ b/smash/web/views/export.py
@@ -5,10 +5,12 @@ import django_excel as excel
 from django.http import HttpResponse
 
 from notifications import get_today_midnight_date
+from web.decorators import PermissionDecorator
 from . import e500_error, wrap_response
 from ..models import Subject, StudySubject, Appointment
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export_to_csv(request, data_type="subjects"):
     # Create the HttpResponse object with the appropriate CSV header.
     selected_fields = request.GET.get('fields', None)
@@ -29,6 +31,7 @@ def export_to_csv(request, data_type="subjects"):
     return response
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export_to_excel(request, data_type="subjects"):
     selected_fields = request.GET.get('fields', None)
     filename = data_type + '-' + get_today_midnight_date().strftime("%Y-%m-%d") + ".xls"
@@ -53,26 +56,27 @@ class CustomField:
 
 DROP_OUT_FIELD = CustomField({'verbose_name': "DROP OUT", 'name': "custom-drop-out"})
 APPOINTMENT_TYPE_FIELD = CustomField({
-        'name': 'appointment_types',
-        'verbose_name': 'Appointment Types'
-    })
+    'name': 'appointment_types',
+    'verbose_name': 'Appointment Types'
+})
 STUDY_SUBJECT_FIELDS = [CustomField({
-        'name': 'nd_number',
-        'verbose_name' : 'ND number'
-    })]
+    'name': 'nd_number',
+    'verbose_name': 'ND number'
+})]
 
 SUBJECT_FIELDS = [CustomField({
-        'name': 'last_name',
-        'verbose_name': 'Family name'
-    }),
+    'name': 'last_name',
+    'verbose_name': 'Family name'
+}),
     CustomField({
         'name': 'first_name',
         'verbose_name': 'Name'
     })]
 VISIT_FIELDS = [CustomField({
-        'name': 'visit_number',
-        'verbose_name': 'Visit'
-    })]
+    'name': 'visit_number',
+    'verbose_name': 'Visit'
+})]
+
 
 def filter_fields_from_selected_fields(fields, selected_fields):
     if selected_fields is None:
@@ -80,6 +84,7 @@ def filter_fields_from_selected_fields(fields, selected_fields):
     selected_fields = set(selected_fields.split(','))
     return [field for field in fields if field.name in selected_fields]
 
+
 def get_default_subject_fields():
     subject_fields = []
     for field in Subject._meta.fields:
@@ -91,12 +96,13 @@ def get_default_subject_fields():
     subject_fields.append(DROP_OUT_FIELD)
     return subject_fields
 
+
 def get_subjects_as_array(selected_fields=None):
     result = []
-    subject_fields = get_default_subject_fields() 
+    subject_fields = get_default_subject_fields()
     subject_fields = filter_fields_from_selected_fields(subject_fields, selected_fields)
 
-    field_names = [field.verbose_name for field in subject_fields] #faster than loop
+    field_names = [field.verbose_name for field in subject_fields]  # faster than loop
     result.append(field_names)
 
     subjects = StudySubject.objects.order_by('-subject__last_name')
@@ -105,6 +111,7 @@ def get_subjects_as_array(selected_fields=None):
         result.append([unicode(s).replace("\n", ";").replace("\r", ";") for s in row])
     return result
 
+
 def subject_to_row_for_fields(study_subject, subject_fields):
     row = []
     for field in subject_fields:
@@ -128,31 +135,33 @@ def subject_to_row_for_fields(study_subject, subject_fields):
         row.append(cell)
     return row
 
+
 def get_appointment_fields():
     appointments_fields = []
     for field in Appointment._meta.fields:
         if field.name.upper() != "VISIT" and field.name.upper() != "ID" and \
-                        field.name.upper() != "WORKER_ASSIGNED" and field.name.upper() != "APPOINTMENT_TYPES" and \
-                        field.name.upper() != "ROOM" and field.name.upper() != "FLYING_TEAM":
+                field.name.upper() != "WORKER_ASSIGNED" and field.name.upper() != "APPOINTMENT_TYPES" and \
+                field.name.upper() != "ROOM" and field.name.upper() != "FLYING_TEAM":
             appointments_fields.append(field)
 
     all_fields = STUDY_SUBJECT_FIELDS + SUBJECT_FIELDS + VISIT_FIELDS + appointments_fields + [APPOINTMENT_TYPE_FIELD]
 
     return all_fields, appointments_fields
 
+
 def get_appointments_as_array(selected_fields=None):
     result = []
     all_fields, appointments_fields = get_appointment_fields()
     all_fields = filter_fields_from_selected_fields(all_fields, selected_fields)
     appointments_fields = filter_fields_from_selected_fields(appointments_fields, selected_fields)
 
-    field_names = [field.verbose_name for field in all_fields] #faster than loop
+    field_names = [field.verbose_name for field in all_fields]  # faster than loop
     result.append(field_names)
 
     appointments = Appointment.objects.order_by('-datetime_when')
 
     for appointment in appointments:
-        #add field_names ['ND number', 'Family name', 'Name', 'Visit'] first
+        # add field_names ['ND number', 'Family name', 'Name', 'Visit'] first
         row = []
         for field in STUDY_SUBJECT_FIELDS:
             if field.verbose_name in field_names:
@@ -175,15 +184,16 @@ def get_appointments_as_array(selected_fields=None):
         for field in appointments_fields:
             row.append(getattr(appointment, field.name))
         if APPOINTMENT_TYPE_FIELD.verbose_name in field_names:
-            #avoid last comma in the list of appointment types
+            # avoid last comma in the list of appointment types
             type_string = ','.join([appointment_type.code for appointment_type in appointment.appointment_types.all()])
             row.append(type_string)
             result.append([unicode(s).replace("\n", ";").replace("\r", ";") for s in row])
     return result
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export(request):
     return wrap_response(request, 'export/index.html', {
         'subject_fields': get_default_subject_fields(),
         'appointment_fields': get_appointment_fields()[0]
-    })
\ No newline at end of file
+    })
-- 
GitLab


From 19258de343bc6d459a410660421ba707709bfe04 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Thu, 19 Mar 2020 16:29:25 +0100
Subject: [PATCH 09/10] default permission for existing workers

---
 ...145_add_permissions_to_existing_workers.py | 24 +++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 smash/web/migrations/0145_add_permissions_to_existing_workers.py

diff --git a/smash/web/migrations/0145_add_permissions_to_existing_workers.py b/smash/web/migrations/0145_add_permissions_to_existing_workers.py
new file mode 100644
index 00000000..877a3754
--- /dev/null
+++ b/smash/web/migrations/0145_add_permissions_to_existing_workers.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0144_auto_20200319_1518'),
+    ]
+
+    operations = [
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='view_daily_planning';"),
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='change_flyingteam';"),
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='export_subjects';"),
+
+    ]
-- 
GitLab


From 1af70bd096bccb9406f6f692b2f9cc81563a0ac4 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Fri, 20 Mar 2020 08:40:05 +0100
Subject: [PATCH 10/10] permissions grouped

---
 smash/web/templates/sidebar.html    | 2 +-
 smash/web/views/__init__.py         | 3 ++-
 smash/web/views/appointment_type.py | 8 ++++----
 smash/web/views/equipment.py        | 8 ++++----
 smash/web/views/flying_teams.py     | 6 +++---
 smash/web/views/kit.py              | 6 +++---
 smash/web/views/rooms.py            | 8 ++++----
 7 files changed, 21 insertions(+), 20 deletions(-)

diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index 9d215925..a5753199 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -34,7 +34,7 @@
     </li>
     {% endif %}
 
-    {% if "change_item" in permissions or "change_appointmenttype" in permissions or "change_appointmenttype" in permissions or "change_flyingteam" in permissions or "send_sample_mail_for_appointments" in permissions or "change_room" in permissions %}
+    {% if equipment_perms %}
         <li data-desc="equipment_and_rooms" class="treeview">
             <a href="{% url 'web.views.equipment_and_rooms' %}">
                 <i class="fa fa-building-o"></i> <span>Equipment &amp; Rooms</span>
diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py
index 6fc26504..f662f7e7 100644
--- a/smash/web/views/__init__.py
+++ b/smash/web/views/__init__.py
@@ -52,7 +52,7 @@ def extend_context(params, request):
     else:
         #use full name if available, username otherwise
         if len(request.user.get_full_name()) > 1:
-            person = request.user.get_full_name()  
+            person = request.user.get_full_name()
         else:
             person = request.user.get_username()
         role   = '<No worker information>'
@@ -61,6 +61,7 @@ def extend_context(params, request):
     final_params.update({
         'permissions' : permissions,
         'conf_perms'  : permissions & PermissionDecorator.codename_groups['configuration'],
+        'equipment_perms'  : permissions & PermissionDecorator.codename_groups['equipment'],
         'person': person,
         'role': role,
         'notifications': notifications,
diff --git a/smash/web/views/appointment_type.py b/smash/web/views/appointment_type.py
index 9aa924d1..2637c26a 100644
--- a/smash/web/views/appointment_type.py
+++ b/smash/web/views/appointment_type.py
@@ -11,7 +11,7 @@ class AppointmentTypeListView(ListView, WrappedView):
     template_name = 'appointment_types/index.html'
     context_object_name = "appointment_types"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeListView, self).dispatch(*args, **kwargs)
 
@@ -22,7 +22,7 @@ class AppointmentTypeCreateView(CreateView, WrappedView):
     success_url = reverse_lazy('web.views.appointment_types')
     success_message = "Appointment type created"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeCreateView, self).dispatch(*args, **kwargs)
 
@@ -34,7 +34,7 @@ class AppointmentTypeEditView(UpdateView, WrappedView):
     template_name = "appointment_types/edit.html"
     context_object_name = "appointment_types"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeEditView, self).dispatch(*args, **kwargs)
 
@@ -47,6 +47,6 @@ class AppointmentTypeDeleteView(DeleteView, WrappedView):
         messages.success(request, "Appointment Type deleted")
         return super(AppointmentTypeDeleteView, self).delete(request, *args, **kwargs)
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeDeleteView, self).dispatch(*args, **kwargs)
\ No newline at end of file
diff --git a/smash/web/views/equipment.py b/smash/web/views/equipment.py
index a4c36568..4d006e41 100644
--- a/smash/web/views/equipment.py
+++ b/smash/web/views/equipment.py
@@ -7,7 +7,7 @@ from ..forms.forms import ItemForm
 from ..models import Item
 
 
-@PermissionDecorator('change_item', 'item')
+@PermissionDecorator('change_item', 'equipment')
 def equipment(request):
     equipment_list = Item.objects.order_by('-name')
     context = {
@@ -17,7 +17,7 @@ def equipment(request):
     return wrap_response(request, "equipment_and_rooms/equipment/index.html", context)
 
 
-@PermissionDecorator('change_item', 'item')
+@PermissionDecorator('change_item', 'equipment')
 def equipment_add(request):
     if request.method == 'POST':
         form = ItemForm(request.POST)
@@ -30,7 +30,7 @@ def equipment_add(request):
     return wrap_response(request, 'equipment_and_rooms/equipment/add.html', {'form': form})
 
 
-@PermissionDecorator('change_item', 'item')
+@PermissionDecorator('change_item', 'equipment')
 def equipment_edit(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     if request.method == 'POST':
@@ -44,7 +44,7 @@ def equipment_edit(request, equipment_id):
     return wrap_response(request, 'equipment_and_rooms/equipment/edit.html', {'form': form})
 
 
-@PermissionDecorator('change_item', 'item')
+@PermissionDecorator('change_item', 'equipment')
 def equipment_delete(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     the_item.delete()
diff --git a/smash/web/views/flying_teams.py b/smash/web/views/flying_teams.py
index e96fe301..b0f5c7b0 100644
--- a/smash/web/views/flying_teams.py
+++ b/smash/web/views/flying_teams.py
@@ -7,7 +7,7 @@ from ..forms.forms import FlyingTeamAddForm, FlyingTeamEditForm
 from ..models import FlyingTeam
 
 
-@PermissionDecorator('change_flyingteam', 'flyingteam')
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams(request):
     flying_team_list = FlyingTeam.objects.order_by('-place')
     context = {
@@ -19,7 +19,7 @@ def flying_teams(request):
                          context)
 
 
-@PermissionDecorator('change_flyingteam', 'flyingteam')
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams_add(request):
     if request.method == 'POST':
         form = FlyingTeamAddForm(request.POST)
@@ -32,7 +32,7 @@ def flying_teams_add(request):
     return wrap_response(request, 'equipment_and_rooms/flying_teams/add.html', {'form': form})
 
 
-@PermissionDecorator('change_flyingteam', 'flyingteam')
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams_edit(request, flying_team_id):
     the_flying_team = get_object_or_404(FlyingTeam, id=flying_team_id)
     if request.method == 'POST':
diff --git a/smash/web/views/kit.py b/smash/web/views/kit.py
index 159338fa..c099d903 100644
--- a/smash/web/views/kit.py
+++ b/smash/web/views/kit.py
@@ -61,7 +61,7 @@ def get_kit_requests(user, start_date=None, end_date=None):
     return result
 
 
-@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def get_kit_requests_data(request, start_date=None, end_date=None):
     form = KitRequestForm()
     if request.method == 'POST':
@@ -78,7 +78,7 @@ def get_kit_requests_data(request, start_date=None, end_date=None):
     return params
 
 
-@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def kit_requests(request):
     return wrap_response(request, 'equipment_and_rooms/kit_requests/kit_requests.html', get_kit_requests_data(request))
 
@@ -139,7 +139,7 @@ def send_mail(data):
     EmailSender().send_email(title, email_body, recipients, cc_recipients)
 
 
-@PermissionDecorator('send_sample_mail_for_appointments', 'appointment')
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def kit_requests_send_mail(request, start_date, end_date=None):
     data = get_kit_requests_data(request, start_date, end_date)
     try:
diff --git a/smash/web/views/rooms.py b/smash/web/views/rooms.py
index 5c20463b..a5ca9193 100644
--- a/smash/web/views/rooms.py
+++ b/smash/web/views/rooms.py
@@ -7,7 +7,7 @@ from ..forms.forms import RoomForm
 from ..models import Room
 
 
-@PermissionDecorator('change_room', 'room')
+@PermissionDecorator('change_room', 'equipment')
 def rooms(request):
     rooms_list = Room.objects.order_by('-city')
     context = {
@@ -19,7 +19,7 @@ def rooms(request):
                          context)
 
 
-@PermissionDecorator('change_room', 'room')
+@PermissionDecorator('change_room', 'equipment')
 def rooms_add(request):
     if request.method == 'POST':
         form = RoomForm(request.POST)
@@ -32,7 +32,7 @@ def rooms_add(request):
     return wrap_response(request, 'equipment_and_rooms/rooms/add.html', {'form': form})
 
 
-@PermissionDecorator('change_room', 'room')
+@PermissionDecorator('change_room', 'equipment')
 def rooms_edit(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     if request.method == 'POST':
@@ -46,7 +46,7 @@ def rooms_edit(request, room_id):
     return wrap_response(request, 'equipment_and_rooms/rooms/edit.html', {'form': form})
 
 
-@PermissionDecorator('change_room', 'room')
+@PermissionDecorator('change_room', 'equipment')
 def rooms_delete(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     the_room.delete()
-- 
GitLab