diff --git a/pepfar_mle/common/tests/test_views.py b/pepfar_mle/common/tests/test_views.py index 7e82d988..0464be55 100644 --- a/pepfar_mle/common/tests/test_views.py +++ b/pepfar_mle/common/tests/test_views.py @@ -9,8 +9,8 @@ pytestmark = pytest.mark.django_db -def test_approved_mixin_approved_user(rf, django_user_model): - approved_user = baker.make(django_user_model, is_approved=True) +def test_approved_mixin_approved_user(rf, user_with_all_permissions): + approved_user = user_with_all_permissions url = "/" request = rf.get(url) request.user = approved_user @@ -32,29 +32,29 @@ def test_approved_mixin_non_approved_authenticated_user(rf, django_user_model): assert "PermissionDenied" in str(e) -def test_home_view(user, client): - client.force_login(user) +def test_home_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("home") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_about_view(user, client): - client.force_login(user) +def test_about_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("about") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_facility_view(user, client): - client.force_login(user) +def test_facility_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("common:facilities") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_systems_view(user, client): - client.force_login(user) +def test_systems_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("common:systems") response = client.get(url) assert response.status_code == status.HTTP_200_OK diff --git a/pepfar_mle/common/views.py b/pepfar_mle/common/views.py index 47abc85b..6e07458d 100644 --- a/pepfar_mle/common/views.py +++ b/pepfar_mle/common/views.py @@ -1,9 +1,13 @@ -from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.contrib.auth.mixins import ( + LoginRequiredMixin, + PermissionRequiredMixin, + UserPassesTestMixin, +) from django.views.generic import TemplateView, View -class ApprovedMixin(UserPassesTestMixin, View): - permission_denied_message = "Your account is pending approval" +class ApprovedMixin(UserPassesTestMixin, PermissionRequiredMixin, View): + permission_denied_message = "Permission Denied" def test_func(self): return self.request.user.is_authenticated and self.request.user.is_approved @@ -11,6 +15,7 @@ def test_func(self): class HomeView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/home.html" + permission_required = "users.can_view_dashboard" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -20,6 +25,7 @@ def get_context_data(self, **kwargs): class AboutView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/about.html" + permission_required = "users.can_view_about" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -29,6 +35,7 @@ def get_context_data(self, **kwargs): class FacilityView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/common/facilities.html" + permission_required = "common.view_facility" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -39,6 +46,7 @@ def get_context_data(self, **kwargs): class SystemsView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/common/systems.html" + permission_required = "common.view_system" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/pepfar_mle/conftest.py b/pepfar_mle/conftest.py index e59a3ab8..7de48e8f 100644 --- a/pepfar_mle/conftest.py +++ b/pepfar_mle/conftest.py @@ -1,4 +1,6 @@ import pytest +from django.contrib.auth.models import Group, Permission +from model_bakery import baker from pepfar_mle.users.models import User from pepfar_mle.users.tests.factories import UserFactory @@ -12,3 +14,30 @@ def media_storage(settings, tmpdir): @pytest.fixture def user() -> User: return UserFactory() + + +@pytest.fixture +def user_with_all_permissions(user) -> User: + all_perms = Permission.objects.all() + for perm in all_perms: + user.user_permissions.add(perm) + user.save() + return user + + +@pytest.fixture +def group_with_all_permissions() -> Group: + group = baker.make(Group) + all_perms = Permission.objects.all() + for perm in all_perms: + group.permissions.add(perm) + + group.save() + return group + + +@pytest.fixture +def user_with_group(user, group_with_all_permissions) -> User: + user.groups.add(group_with_all_permissions) + user.save() + return user diff --git a/pepfar_mle/ops/migrations/0002_auto_20210719_1249.py b/pepfar_mle/ops/migrations/0002_auto_20210719_1249.py new file mode 100644 index 00000000..5aad4a73 --- /dev/null +++ b/pepfar_mle/ops/migrations/0002_auto_20210719_1249.py @@ -0,0 +1,166 @@ +# Generated by Django 3.2.5 on 2021-07-19 09:49 + +from django.conf import settings +import django.contrib.postgres.fields +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('common', '0007_alter_system_options'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ops', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('name', models.CharField(max_length=64)), + ('description', models.TextField()), + ('deadline', models.DateField()), + ('is_complete', models.BooleanField(default=False)), + ], + options={ + 'ordering': ('-updated', '-created'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='WeeklyProgramUpdate', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('date', models.DateField()), + ('attendees', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)), + ('comments', models.TextField()), + ('activity', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='ops.activity')), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_weeklyprogramupdate_related', to='common.organisation')), + ], + options={ + 'ordering': ('-updated', '-created'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='SiteMentorship', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('day', models.DateField()), + ('start', models.TimeField()), + ('end', models.TimeField()), + ('objective', models.TextField()), + ('pick_up_point', models.TextField()), + ('drop_off_point', models.TextField()), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_sitementorship_related', to='common.organisation')), + ('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='common.facility')), + ('staff_member', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-day', '-end', '-start', 'site__name'), + }, + ), + migrations.CreateModel( + name='OperationalArea', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('name', models.CharField(max_length=64)), + ('description', models.TextField()), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_operationalarea_related', to='common.organisation')), + ], + options={ + 'ordering': ('-updated', '-created'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='DailyUpdate', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('date', models.DateField()), + ('total', models.IntegerField()), + ('clients_booked', models.IntegerField()), + ('kept_appointment', models.IntegerField()), + ('missed_appointment', models.IntegerField()), + ('came_early', models.IntegerField()), + ('unscheduled', models.IntegerField()), + ('new_ft', models.IntegerField()), + ('ipt_new_adults', models.IntegerField()), + ('ipt_new_paeds', models.IntegerField()), + ('facility', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='common.facility')), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_dailyupdate_related', to='common.organisation')), + ], + options={ + 'ordering': ('-updated', '-created'), + 'abstract': False, + }, + ), + migrations.CreateModel( + name='ActivityLog', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('active', models.BooleanField(default=True)), + ('created', models.DateTimeField(default=django.utils.timezone.now)), + ('created_by', models.UUIDField(blank=True, null=True)), + ('updated', models.DateTimeField(default=django.utils.timezone.now)), + ('updated_by', models.UUIDField(blank=True, null=True)), + ('activity', models.TextField(help_text='Activity as budgeted for')), + ('planned_date', models.DateField(help_text='Planned date for the activity')), + ('requested_date', models.DateField(help_text='Date requested')), + ('procurement_date', models.DateField(help_text='Date received by procurement')), + ('finance_approval_date', models.DateField(help_text='Date received by Finance for approvals')), + ('final_approval_date', models.DateField(help_text='Date approved by COP/DCOP/FAD')), + ('done_date', models.DateField(help_text='Date when activity/procurement done')), + ('invoiced_date', models.DateField(help_text='Date when payment invoice was submitted to Finance')), + ('remarks', models.TextField()), + ('organisation', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_activitylog_related', to='common.organisation')), + ], + options={ + 'ordering': ('-requested_date', '-planned_date', '-procurement_date'), + }, + ), + migrations.AddField( + model_name='activity', + name='area', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='ops.operationalarea'), + ), + migrations.AddField( + model_name='activity', + name='organisation', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='ops_activity_related', to='common.organisation'), + ), + migrations.AddField( + model_name='activity', + name='responsibility', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/pepfar_mle/ops/models.py b/pepfar_mle/ops/models.py index f5b0c6f2..b397609f 100644 --- a/pepfar_mle/ops/models.py +++ b/pepfar_mle/ops/models.py @@ -1,9 +1,12 @@ +from django.contrib.auth import get_user_model +from django.contrib.postgres.fields import ArrayField from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from pepfar_mle.common.models import AbstractBase, Facility, System -from pepfar_mle.users.models import User + +User = get_user_model() class TimeSheet(AbstractBase): @@ -98,3 +101,111 @@ class Meta: "-raised", "-resolved", ) + + +class ActivityLog(AbstractBase): + activity = models.TextField(help_text="Activity as budgeted for") + planned_date = models.DateField(help_text="Planned date for the activity") + requested_date = models.DateField(help_text="Date requested") + procurement_date = models.DateField(help_text="Date received by procurement") + finance_approval_date = models.DateField(help_text="Date received by Finance for approvals") + final_approval_date = models.DateField(help_text="Date approved by COP/DCOP/FAD") + done_date = models.DateField(help_text="Date when activity/procurement done") + invoiced_date = models.DateField( + help_text="Date when payment invoice was submitted to Finance" + ) + remarks = models.TextField() + + class Meta: + ordering = ( + "-requested_date", + "-planned_date", + "-procurement_date", + ) + + +class SiteMentorship(AbstractBase): + staff_member = models.ForeignKey(User, on_delete=models.PROTECT) + day = models.DateField() + start = models.TimeField() + end = models.TimeField() + site = models.ForeignKey(Facility, on_delete=models.PROTECT) + objective = models.TextField() + pick_up_point = models.TextField() + drop_off_point = models.TextField() + + class Meta: + ordering = ( + "-day", + "-end", + "-start", + "site__name", + ) + + +class DailyUpdate(AbstractBase): + """ + Daily updates from facilities. + + e.g + + 16/6/2021 + Clients booked -18 + Kept appointment -17 + Came early -0 + Total -17 + Missed appointment- 1 + Unscheduled - 36 + Appointment keeping - 94% + New FT - 0 + IPT New adults- 0 + IPT New paeds - 0 + """ + + facility = models.ForeignKey(Facility, on_delete=models.PROTECT) + date = models.DateField() + total = models.IntegerField() + clients_booked = models.IntegerField() + kept_appointment = models.IntegerField() + missed_appointment = models.IntegerField() + came_early = models.IntegerField() + unscheduled = models.IntegerField() + new_ft = models.IntegerField() + ipt_new_adults = models.IntegerField() + ipt_new_paeds = models.IntegerField() + + @property + def appointment_keeping(self): + if self.clients_booked == 0: + return 0 + + return (self.kept_appointment / self.clients_booked) * 100 + + +class OperationalArea(AbstractBase): + """List of program areas.""" + + name = models.CharField(max_length=64) + description = models.TextField() + + +class Activity(AbstractBase): + area = models.ForeignKey(OperationalArea, on_delete=models.PROTECT) + name = models.CharField(max_length=64) + description = models.TextField() + responsibility = models.ForeignKey(User, on_delete=models.PROTECT) + deadline = models.DateField() + is_complete = models.BooleanField(default=False) + + +class WeeklyProgramUpdate(AbstractBase): + """ + Record of updates made at the weekly "touch base" meetings. + """ + + date = models.DateField() + attendees = ArrayField( + models.TextField(), + ) + activity = models.ForeignKey(Activity, on_delete=models.PROTECT) + comments = models.TextField() diff --git a/pepfar_mle/ops/tests/test_models.py b/pepfar_mle/ops/tests/test_models.py index 5d4f6c11..10ec7fa9 100644 --- a/pepfar_mle/ops/tests/test_models.py +++ b/pepfar_mle/ops/tests/test_models.py @@ -3,7 +3,7 @@ from django.utils import timezone from model_bakery import baker -from pepfar_mle.ops.models import FacilitySystemTicket, TimeSheet +from pepfar_mle.ops.models import DailyUpdate, FacilitySystemTicket, TimeSheet pytestmark = pytest.mark.django_db @@ -64,3 +64,14 @@ def test_facility_ticket_validate_resolved(admin_user): bad_ticket_resolve.save() assert "resolved and resolved_by must both be set" in str(e) + + +def test_daily_update_appointment_keeping_percentage(): + update_with_no_clients_booked = baker.make(DailyUpdate, clients_booked=0) + assert update_with_no_clients_booked.appointment_keeping == 0 + + update_with_all_clients_kept = baker.make(DailyUpdate, kept_appointment=5, clients_booked=5) + assert update_with_all_clients_kept.appointment_keeping == 100 + + update_with_half_booking = baker.make(DailyUpdate, kept_appointment=2, clients_booked=4) + assert update_with_half_booking.appointment_keeping == 50 diff --git a/pepfar_mle/ops/tests/test_views.py b/pepfar_mle/ops/tests/test_views.py index 5f5761b3..606c6b4a 100644 --- a/pepfar_mle/ops/tests/test_views.py +++ b/pepfar_mle/ops/tests/test_views.py @@ -5,43 +5,50 @@ pytestmark = pytest.mark.django_db -def test_system_versions_view(user, client): - client.force_login(user) +def test_system_versions_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:versions") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_tickets_view(user, client): - client.force_login(user) +def test_tickets_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:tickets") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_activity_log_view(user, client): - client.force_login(user) +def test_activity_log_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:activity_log") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_site_mentorship_view(user, client): - client.force_login(user) +def test_site_mentorship_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:site_mentorship") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_daily_site_updates_view(user, client): - client.force_login(user) +def test_daily_site_updates_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:daily_site_updates") response = client.get(url) assert response.status_code == status.HTTP_200_OK -def test_timesheets_view(user, client): - client.force_login(user) +def test_timesheets_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) url = reverse("ops:timesheets") response = client.get(url) assert response.status_code == status.HTTP_200_OK + + +def test_weekly_program_updates_view(user_with_all_permissions, client): + client.force_login(user_with_all_permissions) + url = reverse("ops:weekly_program_updates") + response = client.get(url) + assert response.status_code == status.HTTP_200_OK diff --git a/pepfar_mle/ops/urls.py b/pepfar_mle/ops/urls.py index a31593c4..20664b59 100644 --- a/pepfar_mle/ops/urls.py +++ b/pepfar_mle/ops/urls.py @@ -7,6 +7,7 @@ TicketsView, TimeSheetsView, VersionsView, + WeeklyProgramUpdatesView, ) app_name = "ops" @@ -16,5 +17,10 @@ path("activity_log", view=ActivityLogView.as_view(), name="activity_log"), path("site_mentorship", view=SiteMentorshipView.as_view(), name="site_mentorship"), path("daily_site_updates", view=DailySiteUpdatesView.as_view(), name="daily_site_updates"), + path( + "weekly_program_updates", + view=WeeklyProgramUpdatesView.as_view(), + name="weekly_program_updates", + ), path("timesheets", view=TimeSheetsView.as_view(), name="timesheets"), ] diff --git a/pepfar_mle/ops/views.py b/pepfar_mle/ops/views.py index 253e7146..5f3e8186 100644 --- a/pepfar_mle/ops/views.py +++ b/pepfar_mle/ops/views.py @@ -6,6 +6,7 @@ class VersionsView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/ops/versions.html" + permission_required = "ops.view_facilitysystem" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -16,6 +17,7 @@ def get_context_data(self, **kwargs): class TicketsView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/ops/tickets.html" + permission_required = "ops.view_facilitysystemticket" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -24,8 +26,20 @@ def get_context_data(self, **kwargs): return context +class TimeSheetsView(LoginRequiredMixin, ApprovedMixin, TemplateView): + template_name = "pages/ops/timesheets.html" + permission_required = "ops.view_timesheet" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["active"] = "program-nav" # id of active nav element + context["selected"] = "timesheets" # id of selected page + return context + + class ActivityLogView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/ops/activity_log.html" + permission_required = "ops.view_activitylog" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -36,6 +50,7 @@ def get_context_data(self, **kwargs): class SiteMentorshipView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/ops/site_mentorship.html" + permission_required = "ops.view_sitementorship" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -46,6 +61,7 @@ def get_context_data(self, **kwargs): class DailySiteUpdatesView(LoginRequiredMixin, ApprovedMixin, TemplateView): template_name = "pages/ops/daily_site_updates.html" + permission_required = "ops.view_dailyupdate" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -54,11 +70,16 @@ def get_context_data(self, **kwargs): return context -class TimeSheetsView(LoginRequiredMixin, ApprovedMixin, TemplateView): - template_name = "pages/ops/timesheets.html" +class WeeklyProgramUpdatesView(LoginRequiredMixin, ApprovedMixin, TemplateView): + template_name = "pages/ops/weekly_program_updates.html" + permission_required = ( + "ops.view_weeklyprogramupdate", + "ops.view_activity", + "ops.view_operationalarea", + ) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["active"] = "program-nav" # id of active nav element - context["selected"] = "timesheets" # id of selected page + context["selected"] = "weekly-program-updates" # id of selected page return context diff --git a/pepfar_mle/templates/fragments/atoms/dashboard_link.html b/pepfar_mle/templates/fragments/atoms/dashboard_link.html index 53edb55d..a8a71e18 100644 --- a/pepfar_mle/templates/fragments/atoms/dashboard_link.html +++ b/pepfar_mle/templates/fragments/atoms/dashboard_link.html @@ -1,7 +1,9 @@ {% load static i18n compress%} - +{% if perms.users.can_view_dashboard %} + +{% endif %} diff --git a/pepfar_mle/templates/fragments/atoms/facilities_menu.html b/pepfar_mle/templates/fragments/atoms/facilities_menu.html index 9fc97114..b266ac24 100644 --- a/pepfar_mle/templates/fragments/atoms/facilities_menu.html +++ b/pepfar_mle/templates/fragments/atoms/facilities_menu.html @@ -7,18 +7,29 @@
- - Facilities - + {% if perms.common.view_facility %} + + Facilities + + {% endif %} + + {% if perms.common.view_system %} Systems + {% endif %} + + {% if perms.ops.view_facilitysystem %} System Versions + {% endif %} + + {% if perms.ops.view_facilitysystemticket %} Tickets + {% endif %}
diff --git a/pepfar_mle/templates/fragments/atoms/program_menu.html b/pepfar_mle/templates/fragments/atoms/program_menu.html index 58fcd1f6..6ba09c42 100644 --- a/pepfar_mle/templates/fragments/atoms/program_menu.html +++ b/pepfar_mle/templates/fragments/atoms/program_menu.html @@ -7,18 +7,35 @@
- - Activity Log - - - Site Mentorship - - - Daily Site Updates - - - Timesheets - + {% if perms.ops.view_activitylog %} + + Activity Log + + {% endif %} + + {% if perms.ops.view_sitementorship %} + + Site Mentorship + + {% endif %} + + {% if perms.ops.view_dailyupdate %} + + Daily Site Updates + + {% endif %} + + {% if perms.ops.view_timesheet %} + + Timesheets + + {% endif %} + + {% if perms.ops.view_weeklyprogramupdate and perms.ops.view_activity and perms.ops.view_operationalarea %} + + Weekly Program Updates + + {% endif %}
diff --git a/pepfar_mle/templates/fragments/topbar.html b/pepfar_mle/templates/fragments/topbar.html index 0f36ded7..7221718f 100644 --- a/pepfar_mle/templates/fragments/topbar.html +++ b/pepfar_mle/templates/fragments/topbar.html @@ -91,10 +91,12 @@

Weekly Program Updates

+

+ Placeholder page for weekly program updates +

+ ← Back to Dashboard +{% endblock content %} diff --git a/pepfar_mle/users/admin.py b/pepfar_mle/users/admin.py index d3c1607c..7bd76df6 100644 --- a/pepfar_mle/users/admin.py +++ b/pepfar_mle/users/admin.py @@ -31,5 +31,5 @@ class UserAdmin(auth_admin.UserAdmin): ), (_("Important dates"), {"fields": ("last_login", "date_joined")}), ) - list_display = ["username", "name", "is_superuser"] + list_display = ["username", "name", "is_superuser", "permissions", "gps"] search_fields = ["name"] diff --git a/pepfar_mle/users/fixtures/permissions_and_groups.json b/pepfar_mle/users/fixtures/permissions_and_groups.json new file mode 100644 index 00000000..7f15ad2d --- /dev/null +++ b/pepfar_mle/users/fixtures/permissions_and_groups.json @@ -0,0 +1,1417 @@ +[ +{ + "model": "auth.permission", + "fields": { + "name": "Can add permission", + "content_type": [ + "auth", + "permission" + ], + "codename": "add_permission" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change permission", + "content_type": [ + "auth", + "permission" + ], + "codename": "change_permission" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete permission", + "content_type": [ + "auth", + "permission" + ], + "codename": "delete_permission" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view permission", + "content_type": [ + "auth", + "permission" + ], + "codename": "view_permission" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add group", + "content_type": [ + "auth", + "group" + ], + "codename": "add_group" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change group", + "content_type": [ + "auth", + "group" + ], + "codename": "change_group" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete group", + "content_type": [ + "auth", + "group" + ], + "codename": "delete_group" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view group", + "content_type": [ + "auth", + "group" + ], + "codename": "view_group" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add content type", + "content_type": [ + "contenttypes", + "contenttype" + ], + "codename": "add_contenttype" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change content type", + "content_type": [ + "contenttypes", + "contenttype" + ], + "codename": "change_contenttype" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete content type", + "content_type": [ + "contenttypes", + "contenttype" + ], + "codename": "delete_contenttype" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view content type", + "content_type": [ + "contenttypes", + "contenttype" + ], + "codename": "view_contenttype" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add session", + "content_type": [ + "sessions", + "session" + ], + "codename": "add_session" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change session", + "content_type": [ + "sessions", + "session" + ], + "codename": "change_session" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete session", + "content_type": [ + "sessions", + "session" + ], + "codename": "delete_session" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view session", + "content_type": [ + "sessions", + "session" + ], + "codename": "view_session" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add site", + "content_type": [ + "sites", + "site" + ], + "codename": "add_site" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change site", + "content_type": [ + "sites", + "site" + ], + "codename": "change_site" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete site", + "content_type": [ + "sites", + "site" + ], + "codename": "delete_site" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view site", + "content_type": [ + "sites", + "site" + ], + "codename": "view_site" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add email address", + "content_type": [ + "account", + "emailaddress" + ], + "codename": "add_emailaddress" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change email address", + "content_type": [ + "account", + "emailaddress" + ], + "codename": "change_emailaddress" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete email address", + "content_type": [ + "account", + "emailaddress" + ], + "codename": "delete_emailaddress" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view email address", + "content_type": [ + "account", + "emailaddress" + ], + "codename": "view_emailaddress" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add email confirmation", + "content_type": [ + "account", + "emailconfirmation" + ], + "codename": "add_emailconfirmation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change email confirmation", + "content_type": [ + "account", + "emailconfirmation" + ], + "codename": "change_emailconfirmation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete email confirmation", + "content_type": [ + "account", + "emailconfirmation" + ], + "codename": "delete_emailconfirmation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view email confirmation", + "content_type": [ + "account", + "emailconfirmation" + ], + "codename": "view_emailconfirmation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add social account", + "content_type": [ + "socialaccount", + "socialaccount" + ], + "codename": "add_socialaccount" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change social account", + "content_type": [ + "socialaccount", + "socialaccount" + ], + "codename": "change_socialaccount" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete social account", + "content_type": [ + "socialaccount", + "socialaccount" + ], + "codename": "delete_socialaccount" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view social account", + "content_type": [ + "socialaccount", + "socialaccount" + ], + "codename": "view_socialaccount" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add social application", + "content_type": [ + "socialaccount", + "socialapp" + ], + "codename": "add_socialapp" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change social application", + "content_type": [ + "socialaccount", + "socialapp" + ], + "codename": "change_socialapp" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete social application", + "content_type": [ + "socialaccount", + "socialapp" + ], + "codename": "delete_socialapp" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view social application", + "content_type": [ + "socialaccount", + "socialapp" + ], + "codename": "view_socialapp" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add social application token", + "content_type": [ + "socialaccount", + "socialtoken" + ], + "codename": "add_socialtoken" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change social application token", + "content_type": [ + "socialaccount", + "socialtoken" + ], + "codename": "change_socialtoken" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete social application token", + "content_type": [ + "socialaccount", + "socialtoken" + ], + "codename": "delete_socialtoken" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view social application token", + "content_type": [ + "socialaccount", + "socialtoken" + ], + "codename": "view_socialtoken" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add Token", + "content_type": [ + "authtoken", + "token" + ], + "codename": "add_token" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change Token", + "content_type": [ + "authtoken", + "token" + ], + "codename": "change_token" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete Token", + "content_type": [ + "authtoken", + "token" + ], + "codename": "delete_token" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view Token", + "content_type": [ + "authtoken", + "token" + ], + "codename": "view_token" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add token", + "content_type": [ + "authtoken", + "tokenproxy" + ], + "codename": "add_tokenproxy" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change token", + "content_type": [ + "authtoken", + "tokenproxy" + ], + "codename": "change_tokenproxy" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete token", + "content_type": [ + "authtoken", + "tokenproxy" + ], + "codename": "delete_tokenproxy" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view token", + "content_type": [ + "authtoken", + "tokenproxy" + ], + "codename": "view_tokenproxy" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add bookmark", + "content_type": [ + "jet", + "bookmark" + ], + "codename": "add_bookmark" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change bookmark", + "content_type": [ + "jet", + "bookmark" + ], + "codename": "change_bookmark" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete bookmark", + "content_type": [ + "jet", + "bookmark" + ], + "codename": "delete_bookmark" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view bookmark", + "content_type": [ + "jet", + "bookmark" + ], + "codename": "view_bookmark" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add pinned application", + "content_type": [ + "jet", + "pinnedapplication" + ], + "codename": "add_pinnedapplication" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change pinned application", + "content_type": [ + "jet", + "pinnedapplication" + ], + "codename": "change_pinnedapplication" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete pinned application", + "content_type": [ + "jet", + "pinnedapplication" + ], + "codename": "delete_pinnedapplication" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view pinned application", + "content_type": [ + "jet", + "pinnedapplication" + ], + "codename": "view_pinnedapplication" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add log entry", + "content_type": [ + "admin", + "logentry" + ], + "codename": "add_logentry" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change log entry", + "content_type": [ + "admin", + "logentry" + ], + "codename": "change_logentry" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete log entry", + "content_type": [ + "admin", + "logentry" + ], + "codename": "delete_logentry" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view log entry", + "content_type": [ + "admin", + "logentry" + ], + "codename": "view_logentry" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add facility", + "content_type": [ + "common", + "facility" + ], + "codename": "add_facility" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change facility", + "content_type": [ + "common", + "facility" + ], + "codename": "change_facility" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete facility", + "content_type": [ + "common", + "facility" + ], + "codename": "delete_facility" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view facility", + "content_type": [ + "common", + "facility" + ], + "codename": "view_facility" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add organisation", + "content_type": [ + "common", + "organisation" + ], + "codename": "add_organisation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change organisation", + "content_type": [ + "common", + "organisation" + ], + "codename": "change_organisation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete organisation", + "content_type": [ + "common", + "organisation" + ], + "codename": "delete_organisation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view organisation", + "content_type": [ + "common", + "organisation" + ], + "codename": "view_organisation" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add organisation sequence generator", + "content_type": [ + "common", + "organisationsequencegenerator" + ], + "codename": "add_organisationsequencegenerator" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change organisation sequence generator", + "content_type": [ + "common", + "organisationsequencegenerator" + ], + "codename": "change_organisationsequencegenerator" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete organisation sequence generator", + "content_type": [ + "common", + "organisationsequencegenerator" + ], + "codename": "delete_organisationsequencegenerator" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view organisation sequence generator", + "content_type": [ + "common", + "organisationsequencegenerator" + ], + "codename": "view_organisationsequencegenerator" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add facility attachment", + "content_type": [ + "common", + "facilityattachment" + ], + "codename": "add_facilityattachment" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change facility attachment", + "content_type": [ + "common", + "facilityattachment" + ], + "codename": "change_facilityattachment" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete facility attachment", + "content_type": [ + "common", + "facilityattachment" + ], + "codename": "delete_facilityattachment" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view facility attachment", + "content_type": [ + "common", + "facilityattachment" + ], + "codename": "view_facilityattachment" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add user", + "content_type": [ + "users", + "user" + ], + "codename": "add_user" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change user", + "content_type": [ + "users", + "user" + ], + "codename": "change_user" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete user", + "content_type": [ + "users", + "user" + ], + "codename": "delete_user" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view user", + "content_type": [ + "users", + "user" + ], + "codename": "view_user" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add system", + "content_type": [ + "common", + "system" + ], + "codename": "add_system" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change system", + "content_type": [ + "common", + "system" + ], + "codename": "change_system" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete system", + "content_type": [ + "common", + "system" + ], + "codename": "delete_system" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view system", + "content_type": [ + "common", + "system" + ], + "codename": "view_system" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add facility system", + "content_type": [ + "ops", + "facilitysystem" + ], + "codename": "add_facilitysystem" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change facility system", + "content_type": [ + "ops", + "facilitysystem" + ], + "codename": "change_facilitysystem" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete facility system", + "content_type": [ + "ops", + "facilitysystem" + ], + "codename": "delete_facilitysystem" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view facility system", + "content_type": [ + "ops", + "facilitysystem" + ], + "codename": "view_facilitysystem" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add time sheet", + "content_type": [ + "ops", + "timesheet" + ], + "codename": "add_timesheet" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change time sheet", + "content_type": [ + "ops", + "timesheet" + ], + "codename": "change_timesheet" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete time sheet", + "content_type": [ + "ops", + "timesheet" + ], + "codename": "delete_timesheet" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view time sheet", + "content_type": [ + "ops", + "timesheet" + ], + "codename": "view_timesheet" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add facility system ticket", + "content_type": [ + "ops", + "facilitysystemticket" + ], + "codename": "add_facilitysystemticket" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change facility system ticket", + "content_type": [ + "ops", + "facilitysystemticket" + ], + "codename": "change_facilitysystemticket" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete facility system ticket", + "content_type": [ + "ops", + "facilitysystemticket" + ], + "codename": "delete_facilitysystemticket" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view facility system ticket", + "content_type": [ + "ops", + "facilitysystemticket" + ], + "codename": "view_facilitysystemticket" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can View Dashboard", + "content_type": [ + "users", + "user" + ], + "codename": "can_view_dashboard" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can View About Page", + "content_type": [ + "users", + "user" + ], + "codename": "can_view_about" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add activity", + "content_type": [ + "ops", + "activity" + ], + "codename": "add_activity" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change activity", + "content_type": [ + "ops", + "activity" + ], + "codename": "change_activity" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete activity", + "content_type": [ + "ops", + "activity" + ], + "codename": "delete_activity" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view activity", + "content_type": [ + "ops", + "activity" + ], + "codename": "view_activity" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add weekly program update", + "content_type": [ + "ops", + "weeklyprogramupdate" + ], + "codename": "add_weeklyprogramupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change weekly program update", + "content_type": [ + "ops", + "weeklyprogramupdate" + ], + "codename": "change_weeklyprogramupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete weekly program update", + "content_type": [ + "ops", + "weeklyprogramupdate" + ], + "codename": "delete_weeklyprogramupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view weekly program update", + "content_type": [ + "ops", + "weeklyprogramupdate" + ], + "codename": "view_weeklyprogramupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add site mentorship", + "content_type": [ + "ops", + "sitementorship" + ], + "codename": "add_sitementorship" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change site mentorship", + "content_type": [ + "ops", + "sitementorship" + ], + "codename": "change_sitementorship" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete site mentorship", + "content_type": [ + "ops", + "sitementorship" + ], + "codename": "delete_sitementorship" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view site mentorship", + "content_type": [ + "ops", + "sitementorship" + ], + "codename": "view_sitementorship" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add operational area", + "content_type": [ + "ops", + "operationalarea" + ], + "codename": "add_operationalarea" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change operational area", + "content_type": [ + "ops", + "operationalarea" + ], + "codename": "change_operationalarea" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete operational area", + "content_type": [ + "ops", + "operationalarea" + ], + "codename": "delete_operationalarea" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view operational area", + "content_type": [ + "ops", + "operationalarea" + ], + "codename": "view_operationalarea" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add daily update", + "content_type": [ + "ops", + "dailyupdate" + ], + "codename": "add_dailyupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change daily update", + "content_type": [ + "ops", + "dailyupdate" + ], + "codename": "change_dailyupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete daily update", + "content_type": [ + "ops", + "dailyupdate" + ], + "codename": "delete_dailyupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view daily update", + "content_type": [ + "ops", + "dailyupdate" + ], + "codename": "view_dailyupdate" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can add activity log", + "content_type": [ + "ops", + "activitylog" + ], + "codename": "add_activitylog" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can change activity log", + "content_type": [ + "ops", + "activitylog" + ], + "codename": "change_activitylog" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can delete activity log", + "content_type": [ + "ops", + "activitylog" + ], + "codename": "delete_activitylog" + } +}, +{ + "model": "auth.permission", + "fields": { + "name": "Can view activity log", + "content_type": [ + "ops", + "activitylog" + ], + "codename": "view_activitylog" + } +}, +{ + "model": "auth.group", + "fields": { + "name": "Application Super Users", + "permissions": [ + [ + "view_facility", + "common", + "facility" + ], + [ + "view_system", + "common", + "system" + ], + [ + "view_activity", + "ops", + "activity" + ], + [ + "view_activitylog", + "ops", + "activitylog" + ], + [ + "view_dailyupdate", + "ops", + "dailyupdate" + ], + [ + "view_facilitysystem", + "ops", + "facilitysystem" + ], + [ + "view_facilitysystemticket", + "ops", + "facilitysystemticket" + ], + [ + "view_operationalarea", + "ops", + "operationalarea" + ], + [ + "view_sitementorship", + "ops", + "sitementorship" + ], + [ + "view_timesheet", + "ops", + "timesheet" + ], + [ + "view_weeklyprogramupdate", + "ops", + "weeklyprogramupdate" + ], + [ + "can_view_about", + "users", + "user" + ], + [ + "can_view_dashboard", + "users", + "user" + ] + ] + } +} +] diff --git a/pepfar_mle/users/migrations/0004_alter_user_options.py b/pepfar_mle/users/migrations/0004_alter_user_options.py new file mode 100644 index 00000000..77ca7945 --- /dev/null +++ b/pepfar_mle/users/migrations/0004_alter_user_options.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.5 on 2021-07-19 09:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0003_alter_user_is_approved'), + ] + + operations = [ + migrations.AlterModelOptions( + name='user', + options={'permissions': [('can_view_dashboard', 'Can View Dashboard'), ('can_view_about', 'Can View About Page')], 'verbose_name': 'user', 'verbose_name_plural': 'users'}, + ), + ] diff --git a/pepfar_mle/users/models.py b/pepfar_mle/users/models.py index b93e8f42..75594929 100644 --- a/pepfar_mle/users/models.py +++ b/pepfar_mle/users/models.py @@ -20,6 +20,30 @@ class User(AbstractUser): help_text="When true, the user is able to log in to the main website (and vice versa)", ) + @property + def permissions(self): + perms = set( + [ + f"{perm.content_type.app_label}.{perm.codename}" + for perm in self.user_permissions.all() + ] + ) + groups = self.groups.all() + for group in groups: + group_perms = set( + [ + f"{perm.content_type.app_label}.{perm.codename}" + for perm in group.permissions.all() + ] + ) + perms = perms | group_perms + return ",\n".join(list(perms)) or "-" + + @property + def gps(self): + groups = [gp.name for gp in self.groups.all()] + return ",".join(groups) or "-" + def get_absolute_url(self): """Get url for user's detail view. @@ -28,3 +52,9 @@ def get_absolute_url(self): """ return reverse("users:detail", kwargs={"username": self.username}) + + class Meta(AbstractUser.Meta): + permissions = [ + ("can_view_dashboard", "Can View Dashboard"), + ("can_view_about", "Can View About Page"), + ] diff --git a/pepfar_mle/users/tests/test_models.py b/pepfar_mle/users/tests/test_models.py index 345ac9c1..a48a2ce9 100644 --- a/pepfar_mle/users/tests/test_models.py +++ b/pepfar_mle/users/tests/test_models.py @@ -7,3 +7,23 @@ def test_user_get_absolute_url(user: User): assert user.get_absolute_url() == f"/users/{user.username}/" + + +def test_user_permissions_no_permissions(user): + assert user.permissions == "-" + + +def test_user_permissions_with_permissions(user_with_all_permissions): + assert len(user_with_all_permissions.permissions) > 2 + + +def test_user_permissions_with_permissions_via_groups(user_with_group): + assert len(user_with_group.permissions) > 2 + + +def test_user_groups_no_groups(user): + assert user.gps == "-" + + +def test_user_groups_with_groups(user_with_group): + assert len(user_with_group.gps) > 2 diff --git a/pepfar_mle/users/views.py b/pepfar_mle/users/views.py index 37a6efa1..8625330a 100644 --- a/pepfar_mle/users/views.py +++ b/pepfar_mle/users/views.py @@ -10,7 +10,7 @@ User = get_user_model() -class UserDetailView(LoginRequiredMixin, ApprovedMixin, DetailView): +class UserDetailView(LoginRequiredMixin, DetailView): model = User slug_field = "username" diff --git a/requirements/base.txt b/requirements/base.txt index 24eab906..16c0bcae 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,10 +7,10 @@ whitenoise~=5.2.0 # https://github.com/evansd/whitenoise flower~=0.9.7 # https://github.com/mher/flower uvicorn[standard]~=0.14.0 # https://github.com/encode/uvicorn django-phonenumber-field~=5.2.0 -phonenumbers~=8.12.26 +phonenumbers~=8.12.27 Fraction~=1.5.1 coveralls~=3.1.0 -google-cloud-secret-manager~=2.5.0 +google-cloud-secret-manager~=2.6.0 cifrazia-django-jet~=1.1.4 google-api-python-client~=2.13.0 @@ -19,7 +19,7 @@ google-api-python-client~=2.13.0 django~=3.2.5 # pyup: < 3.2 # https://www.djangoproject.com/ django-environ~=0.4.5 # https://github.com/joke2k/django-environ django-model-utils~=4.1.1 # https://github.com/jazzband/django-model-utils -django-allauth~=0.44.0 # https://github.com/pennersr/django-allauth +django-allauth~=0.45.0 # https://github.com/pennersr/django-allauth django-crispy-forms~=1.12.0 # https://github.com/django-crispy-forms/django-crispy-forms django-compressor~=2.4.1 # https://github.com/django-compressor/django-compressor # Django REST Framework