handle DJA ui and handle permission test for UserDomainRole

This commit is contained in:
Rachid Mrad 2024-07-17 15:38:38 -04:00
parent 777be646a4
commit 72ee81a43c
No known key found for this signature in database
3 changed files with 72 additions and 14 deletions

View file

@ -35,6 +35,8 @@ from django_admin_multiple_choice_list_filter.list_filters import MultipleChoice
from import_export import resources from import_export import resources
from import_export.admin import ImportExportModelAdmin from import_export.admin import ImportExportModelAdmin
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.postgres.forms import SimpleArrayField
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -89,6 +91,27 @@ class UserResource(resources.ModelResource):
class Meta: class Meta:
model = models.User model = models.User
class FilteredSelectMultipleArrayWidget(FilteredSelectMultiple):
def __init__(self, verbose_name, is_stacked=False, choices=(), **kwargs):
super().__init__(verbose_name, is_stacked, **kwargs)
self.choices = choices
def value_from_datadict(self, data, files, name):
values = super().value_from_datadict(data, files, name)
# print(f'value_from_datadict - values: {values}')
return values or []
def get_context(self, name, value, attrs):
# print(f'get_context - initial value: {value}')
if value is None:
value = []
elif isinstance(value, str):
value = value.split(',')
# print(f'get_context - processed value: {value}')
self.choices = [(choice, label) for choice, label in self.choices if choice in value] + [(choice, label) for choice, label in self.choices if choice not in value]
# print(f'get_context - choices: {self.choices}')
context = super().get_context(name, value, attrs)
return context
class MyUserAdminForm(UserChangeForm): class MyUserAdminForm(UserChangeForm):
"""This form utilizes the custom widget for its class's ManyToMany UIs. """This form utilizes the custom widget for its class's ManyToMany UIs.
@ -102,6 +125,8 @@ class MyUserAdminForm(UserChangeForm):
widgets = { widgets = {
"groups": NoAutocompleteFilteredSelectMultiple("groups", False), "groups": NoAutocompleteFilteredSelectMultiple("groups", False),
"user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False), "user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False),
"portfolio_roles": FilteredSelectMultipleArrayWidget("portfolio_roles", is_stacked=False, choices=User.UserPortfolioRoleChoices.choices),
"portfolio_additional_permissions": FilteredSelectMultipleArrayWidget("portfolio_additional_permissions", is_stacked=False, choices=User.UserPortfolioPermissionChoices.choices),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -625,6 +650,10 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"status", "status",
) )
# For each filter_horizontal, init in admin js extendFilterHorizontalWidgets
# to activate the edit/delete/view buttons
# filter_horizontal = ("portfolio_roles",)
# Renames inherited AbstractUser label 'email_address to 'email' # Renames inherited AbstractUser label 'email_address to 'email'
def formfield_for_dbfield(self, dbfield, **kwargs): def formfield_for_dbfield(self, dbfield, **kwargs):
field = super().formfield_for_dbfield(dbfield, **kwargs) field = super().formfield_for_dbfield(dbfield, **kwargs)
@ -654,6 +683,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"user_permissions", "user_permissions",
"portfolio", "portfolio",
"portfolio_roles", "portfolio_roles",
"portfolio_additional_permissions",
) )
}, },
), ),
@ -684,6 +714,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"groups", "groups",
"portfolio", "portfolio",
"portfolio_roles", "portfolio_roles",
"portfolio_additional_permissions",
) )
}, },
), ),
@ -715,6 +746,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
"date_joined", "date_joined",
"portfolio", "portfolio",
"portfolio_roles", "portfolio_roles",
"portfolio_additional_permissions",
] ]
list_filter = ( list_filter = (

View file

@ -1,4 +1,4 @@
# Generated by Django 4.2.10 on 2024-07-17 17:12 # Generated by Django 4.2.10 on 2024-07-17 19:10
from django.conf import settings from django.conf import settings
import django.contrib.postgres.fields import django.contrib.postgres.fields
@ -24,6 +24,29 @@ class Migration(migrations.Migration):
to="registrar.portfolio", to="registrar.portfolio",
), ),
), ),
migrations.AddField(
model_name="user",
name="portfolio_additional_permissions",
field=django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(
choices=[
("view_domains", "View all domains and domain reports"),
("edit_domains", "User is a manager on a domain"),
("view_member", "View members"),
("edit_member", "Create and edit members"),
("view_requests", "View requests"),
("edit_requests", "Create and edit requests"),
("view_portfolio", "View organization"),
("edit_portfolio", "Edit organization"),
],
max_length=50,
),
blank=True,
help_text="Select one or more additional permissions.",
null=True,
size=None,
),
),
migrations.AddField( migrations.AddField(
model_name="user", model_name="user",
name="portfolio_roles", name="portfolio_roles",

View file

@ -78,6 +78,7 @@ class User(AbstractUser):
# EDIT_DOMAINS is really self.domains. We add is hear and leverage it in has_permission # EDIT_DOMAINS is really self.domains. We add is hear and leverage it in has_permission
# so we have one way to test for portfolio and domain edit permissions # so we have one way to test for portfolio and domain edit permissions
# Do we need to check for portfolio domains specifically? # Do we need to check for portfolio domains specifically?
# NOTE: A user on an org can currently invite a user outside the org
EDIT_DOMAINS = "edit_domains", "User is a manager on a domain" EDIT_DOMAINS = "edit_domains", "User is a manager on a domain"
VIEW_MEMBER = "view_member", "View members" VIEW_MEMBER = "view_member", "View members"
@ -150,15 +151,15 @@ class User(AbstractUser):
help_text="Select one or more roles.", help_text="Select one or more roles.",
) )
# portfolio_permissions = ArrayField( portfolio_additional_permissions = ArrayField(
# models.CharField( models.CharField(
# max_length=50, max_length=50,
# choices=UserPortfolioPermissionChoices.choices, choices=UserPortfolioPermissionChoices.choices,
# ), ),
# null=True, null=True,
# blank=True, blank=True,
# help_text="Select one or more permissions.", help_text="Select one or more additional permissions.",
# ) )
phone = PhoneNumberField( phone = PhoneNumberField(
null=True, null=True,
@ -256,9 +257,10 @@ class User(AbstractUser):
def has_portfolio_permission(self, portfolio_permission): def has_portfolio_permission(self, portfolio_permission):
"""The views should only call this guy when testing for perms and not rely on roles""" """The views should only call this guy when testing for perms and not rely on roles"""
# TODO: this does not seem to be working # EDIT_DOMAINS === user is a manager on a domain (has UserDomainRole)
# if portfolio_permission == self.UserPortfolioPermissionChoices.EDIT_DOMAINS and self.domains.exists(): # NOTE: Should we check whether the domain is in the portfolio?
# return True if portfolio_permission == self.UserPortfolioPermissionChoices.EDIT_DOMAINS and self.domains.exists():
return True
if not self.portfolio: if not self.portfolio:
return False return False
@ -276,6 +278,7 @@ class User(AbstractUser):
for role in self.portfolio_roles: for role in self.portfolio_roles:
if role in self.PORTFOLIO_ROLE_PERMISSIONS: if role in self.PORTFOLIO_ROLE_PERMISSIONS:
portfolio_permissions.update(self.PORTFOLIO_ROLE_PERMISSIONS[role]) portfolio_permissions.update(self.PORTFOLIO_ROLE_PERMISSIONS[role])
portfolio_permissions.update(self.portfolio_additional_permissions)
return list(portfolio_permissions) # Convert back to list if necessary return list(portfolio_permissions) # Convert back to list if necessary
@classmethod @classmethod