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/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 0cd1210a24..bd4533a798 100644 --- a/hypha/apply/activity/adapters/emails.py +++ b/hypha/apply/activity/adapters/emails.py @@ -11,13 +11,13 @@ 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 ( APPROVER_GROUP_NAME, 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 0e2bd8eb2a..2e18067ae4 100644 --- a/hypha/apply/activity/adapters/utils.py +++ b/hypha/apply/activity/adapters/utils.py @@ -16,12 +16,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 b366e8486a..2d17a8ed26 100644 --- a/hypha/apply/api/v1/serializers.py +++ b/hypha/apply/api/v1/serializers.py @@ -16,7 +16,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 from hypha.core.utils import markdown_to_html User = get_user_model() 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..2c5b60b3ec 100644 --- a/hypha/apply/funds/permissions.py +++ b/hypha/apply/funds/permissions.py @@ -3,7 +3,7 @@ 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 def has_permission(action, user, object=None, raise_exception=True): 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/tests/factories/models.py b/hypha/apply/funds/tests/factories/models.py index 9a740c4154..17164445da 100644 --- a/hypha/apply/funds/tests/factories/models.py +++ b/hypha/apply/funds/tests/factories/models.py @@ -28,7 +28,7 @@ ) from hypha.apply.funds.workflow import ConceptProposal, Request, RequestExternal from hypha.apply.stream_forms.testing.factories import FormDataFactory -from hypha.apply.users.groups import REVIEWER_GROUP_NAME, STAFF_GROUP_NAME +from hypha.apply.users.roles import REVIEWER_GROUP_NAME, STAFF_GROUP_NAME from hypha.apply.users.tests.factories import ( ApplicantFactory, GroupFactory, diff --git a/hypha/apply/funds/tests/test_admin_views.py b/hypha/apply/funds/tests/test_admin_views.py index 492f974f15..dd61ed036f 100644 --- a/hypha/apply/funds/tests/test_admin_views.py +++ b/hypha/apply/funds/tests/test_admin_views.py @@ -6,7 +6,7 @@ from wagtail.test.utils import WagtailTestUtils from hypha.apply.funds.models.forms import ApplicationForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from hypha.apply.users.tests.factories import SuperUserFactory from hypha.home.factories import ApplyHomePageFactory diff --git a/hypha/apply/funds/views_partials.py b/hypha/apply/funds/views_partials.py index 9ecb40fc6c..9e3829666c 100644 --- a/hypha/apply/funds/views_partials.py +++ b/hypha/apply/funds/views_partials.py @@ -24,7 +24,7 @@ from hypha.apply.funds.reviewers.services import get_all_reviewers from hypha.apply.funds.services import annotate_review_recommendation_and_count from hypha.apply.review.options import REVIEWER -from hypha.apply.users.groups import REVIEWER_GROUP_NAME +from hypha.apply.users.roles import REVIEWER_GROUP_NAME from . import services from .models import ApplicationSubmission, Round diff --git a/hypha/apply/projects/forms/project.py b/hypha/apply/projects/forms/project.py index e67b602a72..225c641091 100644 --- a/hypha/apply/projects/forms/project.py +++ b/hypha/apply/projects/forms/project.py @@ -8,7 +8,7 @@ from hypha.apply.funds.models import ApplicationSubmission from hypha.apply.stream_forms.fields import SingleFileField from hypha.apply.stream_forms.forms import StreamBaseForm -from hypha.apply.users.groups import STAFF_GROUP_NAME +from hypha.apply.users.roles import STAFF_GROUP_NAME from ..models.project import ( CLOSING, diff --git a/hypha/apply/projects/service_utils.py b/hypha/apply/projects/service_utils.py index 577d386638..b2ec7cb3d9 100644 --- a/hypha/apply/projects/service_utils.py +++ b/hypha/apply/projects/service_utils.py @@ -12,7 +12,7 @@ remove_tasks_for_user, remove_tasks_for_user_group, ) -from hypha.apply.users.groups import ( +from hypha.apply.users.roles import ( APPROVER_GROUP_NAME, FINANCE_GROUP_NAME, ) diff --git a/hypha/apply/projects/tests/factories.py b/hypha/apply/projects/tests/factories.py index a77b6855a9..590151e04d 100644 --- a/hypha/apply/projects/tests/factories.py +++ b/hypha/apply/projects/tests/factories.py @@ -11,7 +11,7 @@ FormFieldsBlockFactory, NonFileFormFieldsBlockFactory, ) -from hypha.apply.users.groups import APPROVER_GROUP_NAME, STAFF_GROUP_NAME +from hypha.apply.users.roles import APPROVER_GROUP_NAME, STAFF_GROUP_NAME from hypha.apply.users.tests.factories import GroupFactory, StaffFactory, UserFactory from ..models.payment import Invoice, InvoiceDeliverable, SupportingDocument diff --git a/hypha/apply/projects/views/project.py b/hypha/apply/projects/views/project.py index 2b51085e4c..43715fff61 100644 --- a/hypha/apply/projects/views/project.py +++ b/hypha/apply/projects/views/project.py @@ -61,7 +61,7 @@ staff_or_finance_or_contracting_required, staff_required, ) -from hypha.apply.users.groups import CONTRACTING_GROUP_NAME +from hypha.apply.users.roles import CONTRACTING_GROUP_NAME from hypha.apply.utils.models import PDFPageSettings from hypha.apply.utils.storage import PrivateMediaView from hypha.apply.utils.views import DelegateableView, DelegatedViewMixin, ViewDispatcher diff --git a/hypha/apply/review/models.py b/hypha/apply/review/models.py index ec1b1bd166..87d7c0b8eb 100644 --- a/hypha/apply/review/models.py +++ b/hypha/apply/review/models.py @@ -8,7 +8,7 @@ from hypha.apply.funds.models.mixins import AccessFormData from hypha.apply.stream_forms.models import BaseStreamForm -from hypha.apply.users.groups import ( +from hypha.apply.users.roles import ( PARTNER_GROUP_NAME, REVIEWER_GROUP_NAME, STAFF_GROUP_NAME, diff --git a/hypha/apply/users/admin_views.py b/hypha/apply/users/admin_views.py index 4f0fa99313..5b48b8c45e 100644 --- a/hypha/apply/users/admin_views.py +++ b/hypha/apply/users/admin_views.py @@ -4,6 +4,7 @@ from django.db.models import CharField, Q, Value from django.db.models.functions import Coalesce, Lower, NullIf from django.template.defaultfilters import mark_safe +from rolepermissions import roles from wagtail.admin.filters import WagtailFilterSet from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME from wagtail.users.views.groups import GroupViewSet @@ -11,8 +12,6 @@ from wagtail.users.views.users import Index as UserIndexView from wagtail.users.views.users import get_users_filter_query -from .models import GroupDesc - User = get_user_model() # Typically we would check the permission 'auth.change_user' (and 'auth.add_user' / @@ -147,14 +146,17 @@ class CustomGroupIndexView(GroupIndexView): def get_queryset(self): """ - Overriding the normal queryset that would return all Group objects, this returnd an iterable of groups with custom names containing HTML help text. + Overriding the normal queryset that would return all Group objects, this returned an iterable of groups with custom names containing HTML help text. """ group_qs = super().get_queryset() custom_groups = [] for group in group_qs: - help_text = GroupDesc.get_from_group(group) + # Check if the group is a role + help_text = getattr( + roles.registered_roles.get(group.name, {}), "help_text", "" + ) if help_text: group.name = mark_safe( f"{group.name}

{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 17d08f29a1..03dfb257f3 100644 --- a/hypha/apply/users/models.py +++ b/hypha/apply/users/models.py @@ -1,6 +1,6 @@ from django.conf import settings 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 @@ -13,7 +13,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, @@ -381,27 +381,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 2068b91c25..ca17b3d84e 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/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 8682287c4d..c0703929ac 100644 --- a/hypha/settings/django.py +++ b/hypha/settings/django.py @@ -70,6 +70,7 @@ "rest_framework", "rest_framework_api_key", "django_file_form", + "rolepermissions", "hijack", "elevate", # https://django-elevate.readthedocs.io/ "pagedown", @@ -222,6 +223,9 @@ CUSTOM_AUTH_BACKEND, ) +# django-rolepermissions +ROLEPERMISSIONS_MODULE = "hypha.apply.users.roles" + # Default Auto field configuration DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/requirements.txt b/requirements.txt index b0df8fddcb..eff51290e9 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