From 8f16bd2338b509f25c32723b11a336fb7f537bfd Mon Sep 17 00:00:00 2001 From: Saurabh Kumar Date: Thu, 7 Nov 2024 18:15:25 +0530 Subject: [PATCH] Add rolespermissions module, refractor the group and delete submission to use it (#4151) Updates group management: - **Centralized Group Definition:** Instead of creating groups within migration files, which can be cumbersome to maintain, group definitions are now managed through the AbstractRoles model. These roles are synchronized using the `python manage.py sync_roles` command. This ensures no existing groups or their associated permissions are deleted. - **Module Renaming**: The `hypha.apply.users.groups` module has been renamed to `hypha.apply.users.roles` to reflect the shift from group-based to role-based permissions. This aligns with upcoming changes utilizing the `rolepermissions` module. - **Streamlined Group Descriptions**: The GroupDesc model is removed. Instead, help text can be directly defined within the role itself. This simplifies management and allows for translation of group descriptions. **This is the first of a series of pull requests aimed at refactoring the permissions system.** As a sample implementation, converts the delete submission to use this role-permissions. See: https://github.com/HyphaApp/hypha/pull/4151/commits/8239c2172ab9150cdba0db8d95f13e1681f176cd ## Test Steps - make sure the migrations run fine. - groups are create correctly via `python manage.py sync_roles`, it should also keep the existing groups. - groups description is rendered in the admin. - ensure delete submission/application is working as expected --- Procfile | 2 +- conftest.py | 10 ++ docs/setup/deployment/development/docker.md | 5 +- .../deployment/development/stand-alone.md | 2 + docs/setup/deployment/production/heroku.md | 3 +- .../deployment/production/stand-alone.md | 1 + hypha/apply/activity/adapters/emails.py | 4 +- hypha/apply/activity/adapters/utils.py | 4 +- hypha/apply/api/v1/serializers.py | 2 +- .../partials/applicant_submissions.html | 3 +- .../apply/determinations/tests/test_views.py | 10 +- .../seed_community_lab_application.py | 2 +- .../management/commands/seed_concept_note.py | 2 +- .../management/commands/seed_fellowship.py | 2 +- .../commands/seed_rapid_response.py | 2 +- hypha/apply/funds/models/submissions.py | 2 +- hypha/apply/funds/models/utils.py | 2 +- hypha/apply/funds/permissions.py | 29 ++++-- hypha/apply/funds/reviewers/services.py | 2 +- .../funds/applicationsubmission_detail.html | 3 +- .../funds/templatetags/submission_tags.py | 12 --- hypha/apply/funds/tests/factories/models.py | 2 +- hypha/apply/funds/tests/test_admin_views.py | 2 +- hypha/apply/funds/tests/test_views.py | 6 +- .../tests/views/test_submission_delete.py | 62 ++++++++++++ hypha/apply/funds/views.py | 25 +++-- hypha/apply/funds/views_partials.py | 2 +- hypha/apply/projects/forms/project.py | 2 +- hypha/apply/projects/service_utils.py | 2 +- hypha/apply/projects/tests/factories.py | 2 +- hypha/apply/projects/tests/test_views.py | 26 ++--- hypha/apply/projects/views/project.py | 2 +- hypha/apply/review/models.py | 2 +- hypha/apply/users/admin_views.py | 10 +- hypha/apply/users/forms.py | 7 +- hypha/apply/users/groups.py | 93 ------------------ .../management/commands/migrate_users.py | 2 +- .../users/migrations/0002_initial_data.py | 24 +---- .../migrations/0008_add_staff_permissions.py | 10 +- .../migrations/0009_add_partner_group.py | 28 +----- .../0010_add_community_reviewer_group.py | 28 +----- .../migrations/0011_add_applicant_group.py | 28 +----- .../migrations/0012_set_applicant_group.py | 25 +---- .../migrations/0013_add_approver_group.py | 31 +----- .../migrations/0016_add_finance_group.py | 31 +----- .../migrations/0017_rename_staff_admin.py | 25 +---- .../migrations/0018_add_contracting_group.py | 31 +----- .../apply/users/migrations/0021_groupdesc.py | 12 --- .../users/migrations/0024_update_is_staff.py | 6 +- .../users/migrations/0026_delete_groupdesc.py | 15 +++ hypha/apply/users/models.py | 25 +---- hypha/apply/users/roles.py | 98 +++++++++++++++++++ hypha/apply/users/tests/factories.py | 2 +- hypha/apply/users/tests/test_oauth_access.py | 1 - hypha/apply/users/tests/test_views.py | 3 +- hypha/apply/utils/testing/tests.py | 10 +- hypha/home/wagtail_hooks.py | 2 +- hypha/settings/django.py | 5 + hypha/settings/test.py | 2 + requirements.txt | 1 + 60 files changed, 315 insertions(+), 479 deletions(-) create mode 100644 conftest.py create mode 100644 hypha/apply/funds/tests/views/test_submission_delete.py delete mode 100644 hypha/apply/users/groups.py create mode 100644 hypha/apply/users/migrations/0026_delete_groupdesc.py create mode 100644 hypha/apply/users/roles.py diff --git a/Procfile b/Procfile index b073761b7d..c144f8947e 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ -release: python manage.py migrate --noinput && python manage.py clear_cache --cache=default +release: python manage.py migrate --noinput && python manage.py clear_cache --cache=default && python manage.py sync_roles web: gunicorn hypha.wsgi:application --log-file - diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000000..3fdfcf1f3f --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +# This file is used to setup the django environment for pytest +import pytest +from django.core.management import call_command + + +@pytest.fixture(scope="session") +def django_db_setup(django_db_setup, django_db_blocker): + """Create initial groups before running tests.""" + with django_db_blocker.unblock(): + call_command("sync_roles") diff --git a/docs/setup/deployment/development/docker.md b/docs/setup/deployment/development/docker.md index c6549534d3..26854ec246 100644 --- a/docs/setup/deployment/development/docker.md +++ b/docs/setup/deployment/development/docker.md @@ -134,6 +134,7 @@ pg_restore --verbose --clean --if-exists --no-acl --no-owner --dbname=hypha --us After restoring the sandbox db run the migrate command inside the py container. ```shell -docker-compose exec py bash -python3 manage.py migrate +docker-compose exec py python3 manage.py migrate +docker-compose exec py python3 manage.py sync_roles + ``` diff --git a/docs/setup/deployment/development/stand-alone.md b/docs/setup/deployment/development/stand-alone.md index e625b74426..438affb2c9 100644 --- a/docs/setup/deployment/development/stand-alone.md +++ b/docs/setup/deployment/development/stand-alone.md @@ -196,6 +196,7 @@ There are two ways to about it, you can either load demo data from `/public/san ```shell python3 manage.py migrate + python3 manage.py sync_roles ``` === "From Scratch" @@ -209,6 +210,7 @@ There are two ways to about it, you can either load demo data from `/public/san ```text python3 manage.py migrate + python3 manage.py sync_roles ``` !!! tip "Tips" diff --git a/docs/setup/deployment/production/heroku.md b/docs/setup/deployment/production/heroku.md index b56324107e..28e448c972 100644 --- a/docs/setup/deployment/production/heroku.md +++ b/docs/setup/deployment/production/heroku.md @@ -51,12 +51,13 @@ python3 -c "from django.core.management.utils import get_random_secret_key; prin ```shell heroku run python3 manage.py migrate -a [name-of-app] + heroku run python3 manage.py sync_roles -a [name-of-app] heroku run python3 manage.py createcachetable -a [name-of-app] heroku run python3 manage.py createsuperuser -a [name-of-app] heroku run python3 manage.py wagtailsiteupdate [the-public-address] [the-apply-address] 443 -a [name-of-app] ``` -7. Now add the "release" step back to the "Procfile" and deploy again. +7. Now add the "release" step back to the `Procfile` and deploy again. You should now have a running site. diff --git a/docs/setup/deployment/production/stand-alone.md b/docs/setup/deployment/production/stand-alone.md index 57b97fbc9d..2f4fdcb165 100644 --- a/docs/setup/deployment/production/stand-alone.md +++ b/docs/setup/deployment/production/stand-alone.md @@ -152,6 +152,7 @@ npm run build python manage.py collectstatic --noinput python manage.py createcachetable python manage.py migrate --noinput +python manage.py sync_roles python manage.py clear_cache --cache=default python manage.py createsuperuser python manage.py wagtailsiteupdate apply.server.domain 80 diff --git a/hypha/apply/activity/adapters/emails.py b/hypha/apply/activity/adapters/emails.py index 9a7d39b1b7..6fe4df2ede 100644 --- a/hypha/apply/activity/adapters/emails.py +++ b/hypha/apply/activity/adapters/emails.py @@ -11,12 +11,12 @@ from hypha.apply.activity.models import ALL, APPLICANT_PARTNERS, PARTNER from hypha.apply.projects.models.payment import CHANGES_REQUESTED_BY_STAFF, DECLINED from hypha.apply.projects.templatetags.project_tags import display_project_status -from hypha.apply.users.groups import ( +from hypha.apply.users.models import User +from hypha.apply.users.roles import ( CONTRACTING_GROUP_NAME, FINANCE_GROUP_NAME, STAFF_GROUP_NAME, ) -from hypha.apply.users.models import User from hypha.core.mail import ( language, remove_extra_empty_lines, diff --git a/hypha/apply/activity/adapters/utils.py b/hypha/apply/activity/adapters/utils.py index ea3c01574c..8c0ad9a549 100644 --- a/hypha/apply/activity/adapters/utils.py +++ b/hypha/apply/activity/adapters/utils.py @@ -14,12 +14,12 @@ RESUBMITTED, SUBMITTED, ) -from hypha.apply.users.groups import ( +from hypha.apply.users.models import User +from hypha.apply.users.roles import ( CONTRACTING_GROUP_NAME, FINANCE_GROUP_NAME, STAFF_GROUP_NAME, ) -from hypha.apply.users.models import User def link_to(target, request): diff --git a/hypha/apply/api/v1/serializers.py b/hypha/apply/api/v1/serializers.py index ad0e7d4adf..850a8ff3c8 100644 --- a/hypha/apply/api/v1/serializers.py +++ b/hypha/apply/api/v1/serializers.py @@ -14,7 +14,7 @@ ) from hypha.apply.review.models import Review, ReviewOpinion from hypha.apply.review.options import RECOMMENDATION_CHOICES -from hypha.apply.users.groups import PARTNER_GROUP_NAME, STAFF_GROUP_NAME +from hypha.apply.users.roles import PARTNER_GROUP_NAME, STAFF_GROUP_NAME User = get_user_model() diff --git a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html index fbc0488582..412e701f56 100644 --- a/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html +++ b/hypha/apply/dashboard/templates/dashboard/partials/applicant_submissions.html @@ -1,4 +1,5 @@ {% load i18n dashboard_statusbar_tags statusbar_tags workflow_tags heroicons submission_tags %} +{% load can from permission_tags %} {% for submission in page.object_list %}
@@ -28,7 +29,7 @@

{% endif %} {% endif %} - {% user_can_delete_submission submission request.user as can_delete_submission %} + {% can "delete_submission" submission as can_delete_submission %} {% if can_delete_submission %} {% heroicon_micro "trash" class="inline me-1 mt-1 fill-red-600" aria_hidden=true %} diff --git a/hypha/apply/determinations/tests/test_views.py b/hypha/apply/determinations/tests/test_views.py index c62c4c968d..1b10d697cf 100644 --- a/hypha/apply/determinations/tests/test_views.py +++ b/hypha/apply/determinations/tests/test_views.py @@ -69,7 +69,7 @@ def test_can_access_form_if_lead(self): def test_cant_access_wrong_status(self): submission = ApplicationSubmissionFactory(status="rejected") response = self.get_page(submission, "form") - self.assertRedirects(response, self.absolute_url(submission.get_absolute_url())) + self.assertRedirects(response, submission.get_absolute_url()) def test_cant_resubmit_determination(self): submission = ApplicationSubmissionFactory( @@ -81,7 +81,7 @@ def test_cant_resubmit_determination(self): response = self.post_page( submission, {"data": "value", "outcome": determination.outcome}, "form" ) - self.assertRedirects(response, self.absolute_url(submission.get_absolute_url())) + self.assertRedirects(response, submission.get_absolute_url()) def test_can_edit_draft_determination(self): submission = ApplicationSubmissionFactory( @@ -119,7 +119,7 @@ def test_can_edit_draft_determination_if_not_lead(self): submission, {"data": "value", "outcome": determination.outcome}, "form" ) self.assertContains(response, "Approved") - self.assertRedirects(response, self.absolute_url(submission.get_absolute_url())) + self.assertRedirects(response, submission.get_absolute_url()) def test_can_edit_draft_determination_if_not_lead_with_projects(self): submission = ApplicationSubmissionFactory(status="in_discussion") @@ -130,7 +130,7 @@ def test_can_edit_draft_determination_if_not_lead_with_projects(self): submission, {"data": "value", "outcome": determination.outcome}, "form" ) self.assertContains(response, "Approved") - self.assertRedirects(response, self.absolute_url(submission.get_absolute_url())) + self.assertRedirects(response, submission.get_absolute_url()) def test_sends_message_if_requires_more_info(self): submission = ApplicationSubmissionFactory( @@ -172,7 +172,7 @@ def test_can_progress_stage_via_determination(self): # Cannot use self.url() as that uses a different base. url = submission_next.get_absolute_url() self.assertRedirects( - response, self.factory.get(url, secure=True).build_absolute_uri(url) + response, self.factory.get(url, secure=False).get_full_path() ) self.assertEqual(submission_original.status, "invited_to_proposal") self.assertEqual(submission_next.status, "draft_proposal") diff --git a/hypha/apply/funds/management/commands/seed_community_lab_application.py b/hypha/apply/funds/management/commands/seed_community_lab_application.py index b62d4d5cf3..f5a2a4ae9c 100644 --- a/hypha/apply/funds/management/commands/seed_community_lab_application.py +++ b/hypha/apply/funds/management/commands/seed_community_lab_application.py @@ -7,7 +7,7 @@ from hypha.apply.funds.models import ApplicationForm, LabType from hypha.apply.funds.models.forms import LabBaseForm, LabBaseReviewForm from hypha.apply.review.models import ReviewForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from hypha.home.models import ApplyHomePage CL_FUND_TITLE = "Community lab (archive fund)" diff --git a/hypha/apply/funds/management/commands/seed_concept_note.py b/hypha/apply/funds/management/commands/seed_concept_note.py index 87ede22bbb..b19b730b2d 100644 --- a/hypha/apply/funds/management/commands/seed_concept_note.py +++ b/hypha/apply/funds/management/commands/seed_concept_note.py @@ -12,7 +12,7 @@ ApplicationBaseReviewForm, ) from hypha.apply.review.models import ReviewForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from hypha.home.models import ApplyHomePage CN_ROUND_TITLE = "Internet Freedom Fund (archive round)" diff --git a/hypha/apply/funds/management/commands/seed_fellowship.py b/hypha/apply/funds/management/commands/seed_fellowship.py index 48225e9691..059ad015b6 100644 --- a/hypha/apply/funds/management/commands/seed_fellowship.py +++ b/hypha/apply/funds/management/commands/seed_fellowship.py @@ -12,7 +12,7 @@ ApplicationBaseReviewForm, ) from hypha.apply.review.models import ReviewForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from hypha.home.models import ApplyHomePage FS_ROUND_TITLE = "Fellowship (archive round)" diff --git a/hypha/apply/funds/management/commands/seed_rapid_response.py b/hypha/apply/funds/management/commands/seed_rapid_response.py index 2659a36cdb..eaeb630705 100644 --- a/hypha/apply/funds/management/commands/seed_rapid_response.py +++ b/hypha/apply/funds/management/commands/seed_rapid_response.py @@ -12,7 +12,7 @@ ApplicationBaseReviewForm, ) from hypha.apply.review.models import ReviewForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from hypha.home.models import ApplyHomePage RR_ROUND_TITLE = "Rapid Response (archive round)" diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index 5d5838cc9a..a1279c4ca3 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -48,7 +48,7 @@ from hypha.apply.stream_forms.models import BaseStreamForm from hypha.apply.todo.options import SUBMISSION_DRAFT from hypha.apply.todo.views import remove_tasks_for_user -from hypha.apply.users.groups import APPLICANT_GROUP_NAME +from hypha.apply.users.roles import APPLICANT_GROUP_NAME from ..blocks import NAMED_BLOCKS, ApplicationCustomFormFieldsBlock from ..workflow import ( diff --git a/hypha/apply/funds/models/utils.py b/hypha/apply/funds/models/utils.py index 6d8081a11a..e304df75fc 100644 --- a/hypha/apply/funds/models/utils.py +++ b/hypha/apply/funds/models/utils.py @@ -17,7 +17,7 @@ from hypha.apply.stream_forms.models import AbstractStreamForm from hypha.apply.todo.options import SUBMISSION_DRAFT from hypha.apply.todo.views import add_task_to_user -from hypha.apply.users.groups import ( +from hypha.apply.users.roles import ( COMMUNITY_REVIEWER_GROUP_NAME, PARTNER_GROUP_NAME, REVIEWER_GROUP_NAME, diff --git a/hypha/apply/funds/permissions.py b/hypha/apply/funds/permissions.py index cd12ba1737..239a41cfd1 100644 --- a/hypha/apply/funds/permissions.py +++ b/hypha/apply/funds/permissions.py @@ -1,9 +1,10 @@ from django.conf import settings from django.core.exceptions import PermissionDenied +from rolepermissions.permissions import register_object_checker from hypha.apply.funds.models.submissions import DRAFT_STATE -from ..users.groups import STAFF_GROUP_NAME, SUPERADMIN, TEAMADMIN_GROUP_NAME +from ..users.roles import STAFF_GROUP_NAME, SUPERADMIN, TEAMADMIN_GROUP_NAME, StaffAdmin def has_permission(action, user, object=None, raise_exception=True): @@ -26,12 +27,27 @@ def can_edit_submission(user, submission): return True, "" -def can_delete_submission(user, submission): +@register_object_checker() +def delete_submission(role, user, submission) -> bool: + """ + Determines if a user has permission to delete a submission. + + Permissions are granted if: + - User is a Superuser, or StaffAdmin + - User has explicit delete_applicationsubmission permission + - User is the applicant of the submission and it is in draft state + """ + if role == StaffAdmin: + return True + if user.has_perm("funds.delete_applicationsubmission"): - return True, "User can delete submission" - elif user == submission.user and submission.status == DRAFT_STATE: - return True, "Applicant can delete draft submissions" - return False, "Forbidden Error" + return True + + # Allow the user to delete their own draft submissions + if user == submission.user and submission.status == DRAFT_STATE: + return True + + return False def can_bulk_delete_submissions(user) -> bool: @@ -174,7 +190,6 @@ def can_view_submission_screening(user, submission): permissions_map = { "submission_view": is_user_has_access_to_view_submission, "submission_edit": can_edit_submission, - "submission_delete": can_delete_submission, "can_view_submission_screening": can_view_submission_screening, "archive_alter": can_alter_archived_submissions, } diff --git a/hypha/apply/funds/reviewers/services.py b/hypha/apply/funds/reviewers/services.py index 84ff8e0ea7..347cc57e01 100644 --- a/hypha/apply/funds/reviewers/services.py +++ b/hypha/apply/funds/reviewers/services.py @@ -2,7 +2,7 @@ from django.db.models import Q from django.db.models.query import QuerySet -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME User = get_user_model() diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html index 5424511c15..82e9f3de10 100644 --- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html +++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html @@ -1,6 +1,7 @@ {% extends "base-apply.html" %} {% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags %} {% load heroicons %} +{% load can from permission_tags %} {% block title %}{{ object.title_text_display }}{% endblock %} {% block body_class %}{% endblock %} @@ -122,7 +123,7 @@
{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio
- {% user_can_delete_submission object request.user as can_delete_submission %} + {% can "delete_submission" object as can_delete_submission %} {% if can_delete_submission %} {help_text}

" diff --git a/hypha/apply/users/forms.py b/hypha/apply/users/forms.py index a72c953a1a..e52415b732 100644 --- a/hypha/apply/users/forms.py +++ b/hypha/apply/users/forms.py @@ -5,9 +5,10 @@ from django.template.defaultfilters import mark_safe from django.utils.translation import gettext_lazy as _ from django_select2.forms import Select2Widget +from rolepermissions import roles from wagtail.users.forms import UserCreationForm, UserEditForm -from .models import AuthSettings, GroupDesc +from .models import AuthSettings from .utils import strip_html_and_nerf_urls User = get_user_model() @@ -125,7 +126,9 @@ def label_from_instance(self, group_obj): """ Overwriting ModelMultipleChoiceField's label from instance to provide help_text (if it exists) """ - help_text = GroupDesc.get_from_group(group_obj) + help_text = getattr( + roles.registered_roles.get(group_obj.name, {}), "help_text", "" + ) if help_text: return mark_safe( f'{group_obj.name}

{help_text}

' diff --git a/hypha/apply/users/groups.py b/hypha/apply/users/groups.py deleted file mode 100644 index 882f02f819..0000000000 --- a/hypha/apply/users/groups.py +++ /dev/null @@ -1,93 +0,0 @@ -from django.utils.translation import gettext_lazy as _ - -SUPERADMIN = _("Administrator") -APPLICANT_GROUP_NAME = _("Applicant") -STAFF_GROUP_NAME = _("Staff") -REVIEWER_GROUP_NAME = _("Reviewer") -TEAMADMIN_GROUP_NAME = _("Staff Admin") -PARTNER_GROUP_NAME = _("Partner") -COMMUNITY_REVIEWER_GROUP_NAME = _("Community reviewer") -APPROVER_GROUP_NAME = _("Approver") -FINANCE_GROUP_NAME = _("Finance") -CONTRACTING_GROUP_NAME = _("Contracting") - -APPLICANT_HELP_TEXT = _( - "Can access their own application and communicate via the communication tab." -) -STAFF_HELP_TEXT = _( - "View and edit all submissions, submit reviews, send determinations, and set up applications." -) -REVIEWER_HELP_TEXT = _( - "Has a dashboard and can submit reviews. Advisory Council Members are typically assigned this role." -) - -TEAMADMIN_HELP_TEXT = _( - "Can view application message log. Must also be in group Staff." -) - -PARTNER_HELP_TEXT = _( - "Can view, edit, and comment on a specific application they are assigned to." -) - -COMMUNITY_REVIEWER_HELP_TEXT = _( - "An applicant with access to other applications utilizing the community/peer review workflow." -) - -APPROVER_HELP_TEXT = _( - "Can review/approve PAF, and access compliance documents. Must also be in group: Staff, Contracting, or Finance." -) -FINANCE_HELP_TEXT = _( - "Can review/approve the PAF, access documents associated with contracting, and access invoices approved by Staff." -) -CONTRACTING_HELP_TEXT = _( - "Can review/approve the PAF and access documents associated with contracting." -) - - -GROUPS = [ - { - "name": APPLICANT_GROUP_NAME, - "permissions": [], - "help_text": APPLICANT_HELP_TEXT, - }, - { - "name": STAFF_GROUP_NAME, - "permissions": [], - "help_text": STAFF_HELP_TEXT, - }, - { - "name": REVIEWER_GROUP_NAME, - "permissions": [], - "help_text": REVIEWER_HELP_TEXT, - }, - { - "name": TEAMADMIN_GROUP_NAME, - "permissions": [], - "help_text": TEAMADMIN_HELP_TEXT, - }, - { - "name": PARTNER_GROUP_NAME, - "permissions": [], - "help_text": PARTNER_HELP_TEXT, - }, - { - "name": COMMUNITY_REVIEWER_GROUP_NAME, - "permissions": [], - "help_text": COMMUNITY_REVIEWER_HELP_TEXT, - }, - { - "name": APPROVER_GROUP_NAME, - "permissions": [], - "help_text": APPROVER_HELP_TEXT, - }, - { - "name": FINANCE_GROUP_NAME, - "permissions": [], - "help_text": FINANCE_HELP_TEXT, - }, - { - "name": CONTRACTING_GROUP_NAME, - "permissions": [], - "help_text": CONTRACTING_HELP_TEXT, - }, -] diff --git a/hypha/apply/users/management/commands/migrate_users.py b/hypha/apply/users/management/commands/migrate_users.py index 2e65c20b0a..59d91389e2 100644 --- a/hypha/apply/users/management/commands/migrate_users.py +++ b/hypha/apply/users/management/commands/migrate_users.py @@ -7,7 +7,7 @@ from django.core.management.base import BaseCommand from django.db import transaction -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME class Command(BaseCommand): diff --git a/hypha/apply/users/migrations/0002_initial_data.py b/hypha/apply/users/migrations/0002_initial_data.py index 1621f68c92..de52563a2e 100644 --- a/hypha/apply/users/migrations/0002_initial_data.py +++ b/hypha/apply/users/migrations/0002_initial_data.py @@ -2,35 +2,15 @@ # Generated by Django 1.11.7 on 2017-12-15 13:15 from __future__ import unicode_literals -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import GROUPS - def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for permission in group_data["permissions"]: - try: - group.permissions.add(Permission.objects.get(codename=permission)) - except ObjectDoesNotExist: - print("Could not find the '%s' permission" % permission) + pass def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - - groups = [group_data["name"] for group_data in GROUPS] - Group.objects.filter(name__in=groups).delete() + pass class Migration(migrations.Migration): diff --git a/hypha/apply/users/migrations/0008_add_staff_permissions.py b/hypha/apply/users/migrations/0008_add_staff_permissions.py index d6be98faac..8f91ec2ebe 100644 --- a/hypha/apply/users/migrations/0008_add_staff_permissions.py +++ b/hypha/apply/users/migrations/0008_add_staff_permissions.py @@ -1,19 +1,11 @@ # Generated by Django 2.0.9 on 2019-01-10 09:28 -from django.contrib.auth.models import Group, Permission from django.db import migrations -from hypha.apply.users.groups import STAFF_GROUP_NAME - class Migration(migrations.Migration): - def add_permissions(apps, schema_editor): - staff_group = Group.objects.get(name=STAFF_GROUP_NAME) - staff_add_perm = Permission.objects.get(name="Can change event") - staff_group.permissions.add(staff_add_perm) - dependencies = [ ("users", "0007_user_slack"), ] - operations = [migrations.RunPython(add_permissions)] + operations = [] diff --git a/hypha/apply/users/migrations/0009_add_partner_group.py b/hypha/apply/users/migrations/0009_add_partner_group.py index a471d8d8f8..eca6859675 100644 --- a/hypha/apply/users/migrations/0009_add_partner_group.py +++ b/hypha/apply/users/migrations/0009_add_partner_group.py @@ -1,38 +1,12 @@ # Generated by Django 2.0.9 on 2018-12-19 13:21 from __future__ import unicode_literals -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import GROUPS, TEAMADMIN_GROUP_NAME, PARTNER_GROUP_NAME - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for permission in group_data["permissions"]: - try: - group.permissions.add(Permission.objects.get(codename=permission)) - except ObjectDoesNotExist: - print("Could not find the '%s' permission" % permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name__in=[TEAMADMIN_GROUP_NAME, PARTNER_GROUP_NAME]).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0008_add_staff_permissions"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0010_add_community_reviewer_group.py b/hypha/apply/users/migrations/0010_add_community_reviewer_group.py index 4829cbaf5c..be39afe9c4 100644 --- a/hypha/apply/users/migrations/0010_add_community_reviewer_group.py +++ b/hypha/apply/users/migrations/0010_add_community_reviewer_group.py @@ -1,38 +1,12 @@ # Generated by Django 2.0.9 on 2018-12-19 13:21 from __future__ import unicode_literals -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import GROUPS, COMMUNITY_REVIEWER_GROUP_NAME - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for permission in group_data["permissions"]: - try: - group.permissions.add(Permission.objects.get(codename=permission)) - except ObjectDoesNotExist: - print("Could not find the '%s' permission" % permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name__in=[COMMUNITY_REVIEWER_GROUP_NAME]).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0009_add_partner_group"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0011_add_applicant_group.py b/hypha/apply/users/migrations/0011_add_applicant_group.py index e55ee34e7d..05a8304744 100644 --- a/hypha/apply/users/migrations/0011_add_applicant_group.py +++ b/hypha/apply/users/migrations/0011_add_applicant_group.py @@ -1,38 +1,12 @@ # Generated by Django 2.0.9 on 2018-12-19 13:21 from __future__ import unicode_literals -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import GROUPS, APPLICANT_GROUP_NAME - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for permission in group_data["permissions"]: - try: - group.permissions.add(Permission.objects.get(codename=permission)) - except ObjectDoesNotExist: - print("Could not find the '%s' permission" % permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name__in=[APPLICANT_GROUP_NAME]).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0010_add_community_reviewer_group"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0012_set_applicant_group.py b/hypha/apply/users/migrations/0012_set_applicant_group.py index 0358424960..38e88013db 100644 --- a/hypha/apply/users/migrations/0012_set_applicant_group.py +++ b/hypha/apply/users/migrations/0012_set_applicant_group.py @@ -1,35 +1,12 @@ # Generated by Django 2.0.9 on 2018-12-19 13:21 from __future__ import unicode_literals -from django.contrib.auth import get_user_model -from django.contrib.auth.models import Group from django.db import migrations -from hypha.apply.users.groups import APPLICANT_GROUP_NAME - - -def set_group(apps, schema_editor): - User = get_user_model() - applicant_group = Group.objects.get(name=APPLICANT_GROUP_NAME) - applicants = User.objects.exclude(applicationsubmission=None) - for user in applicants: - if not user.is_apply_staff: - user.groups.add(applicant_group) - user.save() - - -def unset_group(apps, schema_editor): - User = get_user_model() - applicant_group = Group.objects.get(name=APPLICANT_GROUP_NAME) - applicants = User.objects.filter(groups__name=APPLICANT_GROUP_NAME).all() - for user in applicants: - user.groups.remove(applicant_group) - user.save() - class Migration(migrations.Migration): dependencies = [ ("users", "0011_add_applicant_group"), ] - operations = [migrations.RunPython(set_group, unset_group)] + operations = [] diff --git a/hypha/apply/users/migrations/0013_add_approver_group.py b/hypha/apply/users/migrations/0013_add_approver_group.py index 3638944b2c..5662ab86d4 100644 --- a/hypha/apply/users/migrations/0013_add_approver_group.py +++ b/hypha/apply/users/migrations/0013_add_approver_group.py @@ -1,40 +1,11 @@ # Generated by Django 2.0.13 on 2019-08-05 13:12 -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import APPROVER_GROUP_NAME, GROUPS - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for codename in group_data["permissions"]: - try: - permission = Permission.objects.get(codename=codename) - except ObjectDoesNotExist: - print(f"Could not find the '{permission}' permission") - continue - - group.permissions.add(permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name=APPROVER_GROUP_NAME).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0012_set_applicant_group"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0016_add_finance_group.py b/hypha/apply/users/migrations/0016_add_finance_group.py index 88525ad545..c170781980 100644 --- a/hypha/apply/users/migrations/0016_add_finance_group.py +++ b/hypha/apply/users/migrations/0016_add_finance_group.py @@ -1,40 +1,11 @@ # Generated by Django 2.0.13 on 2019-08-05 13:12 -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import FINANCE_GROUP_NAME, GROUPS - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for codename in group_data["permissions"]: - try: - permission = Permission.objects.get(codename=codename) - except ObjectDoesNotExist: - print(f"Could not find the '{permission}' permission") - continue - - group.permissions.add(permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name=FINANCE_GROUP_NAME).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0015_login_extra_text"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0017_rename_staff_admin.py b/hypha/apply/users/migrations/0017_rename_staff_admin.py index 11ae3ad0a0..ac2c859ff7 100644 --- a/hypha/apply/users/migrations/0017_rename_staff_admin.py +++ b/hypha/apply/users/migrations/0017_rename_staff_admin.py @@ -1,35 +1,12 @@ # Generated by Django 2.0.9 on 2018-12-19 13:21 from __future__ import unicode_literals -from django.contrib.auth.models import Group from django.db import migrations -from hypha.apply.users.groups import TEAMADMIN_GROUP_NAME - - -def rename_group(apps, schema_editor): - try: - team_admin_group = Group.objects.get(name="Team Admin") - except Group.DoesNotExist: - pass - else: - team_admin_group.name = TEAMADMIN_GROUP_NAME - team_admin_group.save() - - -def unrename_group(apps, schema_editor): - try: - team_admin_group = Group.objects.get(name=TEAMADMIN_GROUP_NAME) - except Group.DoesNotExist: - pass - else: - team_admin_group.name = "Team Admin" - team_admin_group.save() - class Migration(migrations.Migration): dependencies = [ ("users", "0016_add_finance_group"), ] - operations = [migrations.RunPython(rename_group, unrename_group)] + operations = [] diff --git a/hypha/apply/users/migrations/0018_add_contracting_group.py b/hypha/apply/users/migrations/0018_add_contracting_group.py index ac220bd2c4..1965ae478e 100644 --- a/hypha/apply/users/migrations/0018_add_contracting_group.py +++ b/hypha/apply/users/migrations/0018_add_contracting_group.py @@ -1,40 +1,11 @@ # Generated by Django 2.0.13 on 2019-08-05 13:12 -from django.core.exceptions import ObjectDoesNotExist -from django.core.management.sql import emit_post_migrate_signal from django.db import migrations -from hypha.apply.users.groups import CONTRACTING_GROUP_NAME, GROUPS - - -def add_groups(apps, schema_editor): - # Workaround for https://code.djangoproject.com/ticket/23422 - db_alias = schema_editor.connection.alias - emit_post_migrate_signal(2, False, db_alias) - - Group = apps.get_model("auth.Group") - Permission = apps.get_model("auth.Permission") - - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - for codename in group_data["permissions"]: - try: - permission = Permission.objects.get(codename=codename) - except ObjectDoesNotExist: - print(f"Could not find the '{permission}' permission") - continue - - group.permissions.add(permission) - - -def remove_groups(apps, schema_editor): - Group = apps.get_model("auth.Group") - Group.objects.filter(name=CONTRACTING_GROUP_NAME).delete() - class Migration(migrations.Migration): dependencies = [ ("users", "0017_rename_staff_admin"), ] - operations = [migrations.RunPython(add_groups, remove_groups)] + operations = [] diff --git a/hypha/apply/users/migrations/0021_groupdesc.py b/hypha/apply/users/migrations/0021_groupdesc.py index 44116250b4..be849fd53b 100644 --- a/hypha/apply/users/migrations/0021_groupdesc.py +++ b/hypha/apply/users/migrations/0021_groupdesc.py @@ -1,19 +1,8 @@ # Generated by Django 3.2.22 on 2023-10-31 17:26 from django.db import migrations, models -from django.contrib.auth.models import Group import django.db.models.deletion -from hypha.apply.users.groups import GROUPS -from hypha.apply.users.models import GroupDesc - - -def add_desc_groups(apps, schema_editor): - for group_data in GROUPS: - group, created = Group.objects.get_or_create(name=group_data["name"]) - if group_data.get("help_text") is not None: - GroupDesc.objects.create(group=group, help_text=group_data["help_text"]) - class Migration(migrations.Migration): dependencies = [ @@ -40,5 +29,4 @@ class Migration(migrations.Migration): ), ], ), - migrations.RunPython(add_desc_groups), ] diff --git a/hypha/apply/users/migrations/0024_update_is_staff.py b/hypha/apply/users/migrations/0024_update_is_staff.py index 09501e7a6b..1bc008cbb7 100644 --- a/hypha/apply/users/migrations/0024_update_is_staff.py +++ b/hypha/apply/users/migrations/0024_update_is_staff.py @@ -3,12 +3,14 @@ from django.db import migrations from django.contrib.auth.models import Group -from hypha.apply.users.groups import TEAMADMIN_GROUP_NAME +from hypha.apply.users.roles import TEAMADMIN_GROUP_NAME from hypha.apply.users.utils import update_is_staff def migrate_is_staff(apps, schema_editor): - group = Group.objects.get(name=TEAMADMIN_GROUP_NAME) + group = Group.objects.filter(name=TEAMADMIN_GROUP_NAME).first() + if group is None: + return users = group.user_set.all() [update_is_staff(None, user) for user in users] diff --git a/hypha/apply/users/migrations/0026_delete_groupdesc.py b/hypha/apply/users/migrations/0026_delete_groupdesc.py new file mode 100644 index 0000000000..73b8de620f --- /dev/null +++ b/hypha/apply/users/migrations/0026_delete_groupdesc.py @@ -0,0 +1,15 @@ +# Generated by Django 4.2.16 on 2024-09-27 05:00 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0025_remove_authsettings_register_extra_text"), + ] + + operations = [ + migrations.DeleteModel( + name="GroupDesc", + ), + ] diff --git a/hypha/apply/users/models.py b/hypha/apply/users/models.py index 82687ef6fd..5e1b77cc15 100644 --- a/hypha/apply/users/models.py +++ b/hypha/apply/users/models.py @@ -1,5 +1,5 @@ from django.contrib.auth.hashers import make_password -from django.contrib.auth.models import AbstractUser, BaseUserManager, Group +from django.contrib.auth.models import AbstractUser, BaseUserManager from django.core import exceptions from django.db import IntegrityError, models from django.db.models.constants import LOOKUP_SEP @@ -12,7 +12,7 @@ from wagtail.contrib.settings.models import BaseGenericSetting, register_setting from wagtail.fields import RichTextField -from .groups import ( +from .roles import ( APPLICANT_GROUP_NAME, APPROVER_GROUP_NAME, COMMUNITY_REVIEWER_GROUP_NAME, @@ -356,27 +356,6 @@ class Meta: ] -class GroupDesc(models.Model): - group = models.OneToOneField(Group, on_delete=models.CASCADE, primary_key=True) - help_text = models.CharField(verbose_name="Help Text", max_length=255) - - @staticmethod - def get_from_group(group_obj: Group) -> str | None: - """ - Get the group description/help text string from a Group object. Returns None if group doesn't have a help text entry. - - Args: - group_obj (Group): The group to retrieve the description of. - """ - try: - return GroupDesc.objects.get(group_id=group_obj.id).help_text - except (exceptions.ObjectDoesNotExist, exceptions.FieldError): - return None - - def __str__(self): - return self.help_text - - class PendingSignup(models.Model): """This model tracks pending passwordless self-signups, and is used to generate a one-time use URLfor each signup. diff --git a/hypha/apply/users/roles.py b/hypha/apply/users/roles.py new file mode 100644 index 0000000000..d8760b0b6d --- /dev/null +++ b/hypha/apply/users/roles.py @@ -0,0 +1,98 @@ +from django.utils.translation import gettext_lazy as _ +from rolepermissions.roles import AbstractUserRole + +SUPERADMIN = _("Administrator") +APPLICANT_GROUP_NAME = _("Applicant") +STAFF_GROUP_NAME = _("Staff") +REVIEWER_GROUP_NAME = _("Reviewer") +TEAMADMIN_GROUP_NAME = _("Staff Admin") +PARTNER_GROUP_NAME = _("Partner") +COMMUNITY_REVIEWER_GROUP_NAME = _("Community reviewer") +APPROVER_GROUP_NAME = _("Approver") +FINANCE_GROUP_NAME = _("Finance") +CONTRACTING_GROUP_NAME = _("Contracting") + + +# roles for the application +# https://django-role-permissions.readthedocs.io/en/stable/roles.html +class Applicant(AbstractUserRole): + role_name = APPLICANT_GROUP_NAME + help_text = _( + "Can access their own application and communicate via " "the communication tab." + ) + + available_permissions = {} + + +class Staff(AbstractUserRole): + role_name = STAFF_GROUP_NAME + help_text = _( + "View and edit all submissions, submit reviews, send determinations, " + "and set up applications." + ) + + available_permissions = {} + + +class Reviewer(AbstractUserRole): + role_name = REVIEWER_GROUP_NAME + help_text = _( + "Has a dashboard and can submit reviews. " + "Advisory Council Members are typically assigned this role." + ) + + available_permissions = {} + + +class StaffAdmin(AbstractUserRole): + role_name = TEAMADMIN_GROUP_NAME + help_text = _("Can view application message log. Must also be in group Staff.") + + available_permissions = {} + + +class Partner(AbstractUserRole): + role_name = PARTNER_GROUP_NAME + help_text = _( + "Can view, edit, and comment on a specific application they are assigned to." + ) + + available_permissions = {} + + +class CommunityReviewer(AbstractUserRole): + role_name = COMMUNITY_REVIEWER_GROUP_NAME + help_text = _( + "An applicant with access to other applications utilizing the community/peer review workflow." + ) + + available_permissions = {} + + +class Approver(AbstractUserRole): + role_name = APPROVER_GROUP_NAME + help_text = _( + "Can review/approve PAF, and access compliance documents. " + "Must also be in group: Staff, Contracting, or Finance." + ) + + available_permissions = {} + + +class Finance(AbstractUserRole): + role_name = FINANCE_GROUP_NAME + help_text = _( + "Can review/approve the PAF, access documents associated with " + "contracting, and access invoices approved by Staff." + ) + + available_permissions = {} + + +class Contracting(AbstractUserRole): + role_name = CONTRACTING_GROUP_NAME + help_text = _( + "Can review/approve the PAF and access documents associated with contracting." + ) + + available_permissions = {} diff --git a/hypha/apply/users/tests/factories.py b/hypha/apply/users/tests/factories.py index e80f89675d..8d151275f6 100644 --- a/hypha/apply/users/tests/factories.py +++ b/hypha/apply/users/tests/factories.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import Group, Permission from django.utils.text import slugify -from ..groups import ( +from ..roles import ( APPLICANT_GROUP_NAME, APPROVER_GROUP_NAME, COMMUNITY_REVIEWER_GROUP_NAME, diff --git a/hypha/apply/users/tests/test_oauth_access.py b/hypha/apply/users/tests/test_oauth_access.py index 34cdbe3e61..6da73c5000 100644 --- a/hypha/apply/users/tests/test_oauth_access.py +++ b/hypha/apply/users/tests/test_oauth_access.py @@ -22,7 +22,6 @@ def test_oauth_page_requires_login(self): self.assertRedirects( response, reverse(settings.LOGIN_URL) + "?next=" + reverse("users:oauth"), - status_code=301, target_status_code=200, ) diff --git a/hypha/apply/users/tests/test_views.py b/hypha/apply/users/tests/test_views.py index 8f6e5501d7..42dc1c9de3 100644 --- a/hypha/apply/users/tests/test_views.py +++ b/hypha/apply/users/tests/test_views.py @@ -26,7 +26,6 @@ def test_cant_access_if_not_logged_in(self): self.assertRedirects( response, reverse(settings.LOGIN_URL) + "?next=" + self.url, - status_code=301, ) def test_has_required_text_and_buttons(self): @@ -68,7 +67,7 @@ class TestPasswordReset(BaseViewTestCase): url_name = "users:{}" base_view_name = "password_reset" - def test_recieves_email(self): + def test_receives_email(self): response = self.post_page(None, data={"email": self.user.email}) self.assertRedirects(response, self.url(None, view_name="password_reset_done")) self.assertEqual(len(mail.outbox), 1) diff --git a/hypha/apply/utils/testing/tests.py b/hypha/apply/utils/testing/tests.py index 1a44d433aa..9623012bbc 100644 --- a/hypha/apply/utils/testing/tests.py +++ b/hypha/apply/utils/testing/tests.py @@ -53,7 +53,7 @@ def setUp(self): def get_kwargs(self, instance): return {} - def url(self, instance, view_name=None, absolute=True, url_kwargs=None): + def url(self, instance, view_name=None, absolute=False, url_kwargs=None): view = view_name or self.base_view_name full_url_name = self.url_name.format(view) kwargs_method = f"get_{view}_kwargs" @@ -63,15 +63,13 @@ def url(self, instance, view_name=None, absolute=True, url_kwargs=None): kwargs = self.get_kwargs(instance) if url_kwargs: kwargs.update(url_kwargs) - return self.url_from_pattern( - full_url_name, kwargs, secure=True, absolute=absolute - ) + return self.url_from_pattern(full_url_name, kwargs, absolute=absolute) - def absolute_url(self, location, secure=True): + def absolute_url(self, location, secure=False): request = self.factory.get(location, secure=secure) return request.build_absolute_uri() - def url_from_pattern(self, pattern, kwargs=None, secure=True, absolute=True): + def url_from_pattern(self, pattern, kwargs=None, secure=False, absolute=False): url = reverse(pattern, kwargs=kwargs) if absolute: return self.absolute_url(url) diff --git a/hypha/home/wagtail_hooks.py b/hypha/home/wagtail_hooks.py index 2987a90540..f4c7fe6771 100644 --- a/hypha/home/wagtail_hooks.py +++ b/hypha/home/wagtail_hooks.py @@ -1,6 +1,6 @@ from wagtail import hooks -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from .models import ApplyHomePage diff --git a/hypha/settings/django.py b/hypha/settings/django.py index 7f97e38f4a..81fa428b5f 100644 --- a/hypha/settings/django.py +++ b/hypha/settings/django.py @@ -69,6 +69,7 @@ "rest_framework", "rest_framework_api_key", "django_file_form", + "rolepermissions", "hijack", "elevate", # https://django-elevate.readthedocs.io/ "pagedown", @@ -221,6 +222,10 @@ CUSTOM_AUTH_BACKEND, ) +# django-rolepermissions +# https://django-role-permissions.readthedocs.io/en/stable/settings.html +ROLEPERMISSIONS_MODULE = "hypha.apply.users.roles" + # Default Auto field configuration DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/hypha/settings/test.py b/hypha/settings/test.py index 51ab2af72b..dc06d9dafa 100644 --- a/hypha/settings/test.py +++ b/hypha/settings/test.py @@ -32,3 +32,5 @@ ELEVATE_COOKIE_SALT = SECRET_KEY ENFORCE_TWO_FACTOR = False + +SECURE_SSL_REDIRECT = False diff --git a/requirements.txt b/requirements.txt index 36fd37fbd8..6380c70f60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,6 +24,7 @@ django-htmx==1.17.3 django-pagedown==2.2.1 django-ratelimit==4.1.0 django-referrer-policy==1.0 +django-role-permissions==3.2.0 django-select2==8.1.2 django-slack==5.19.0 django-storages==1.14.2