Form structure

This commit is contained in:
zandercymatics 2024-12-09 15:03:39 -07:00
parent 8dfb183ce0
commit ed9d215577
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
5 changed files with 201 additions and 71 deletions

View file

@ -110,52 +110,169 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
return cleaned_data
class PortfolioMemberForm(forms.ModelForm):
class BasePortfolioMemberForm(forms.ModelForm):
role = forms.ChoiceField(
label="Select permission",
choices=[
(UserPortfolioRoleChoices.ORGANIZATION_ADMIN.value, "Admin Access"),
(UserPortfolioRoleChoices.ORGANIZATION_MEMBER.value, "Basic Access")
],
widget=forms.RadioSelect,
required=True,
error_messages={
"required": "Member access level is required",
},
)
# Permissions for admins
domain_request_permissions_admin = forms.ChoiceField(
label="Select permission",
choices=[
(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS.value, "View all requests"),
(UserPortfolioPermissionChoices.EDIT_REQUESTS.value, "Create and edit requests")
],
widget=forms.RadioSelect,
required=False,
error_messages={
"required": "Admin domain request permission is required",
},
)
member_permissions_admin = forms.ChoiceField(
label="Select permission",
choices=[
(UserPortfolioPermissionChoices.VIEW_MEMBERS.value, "View all members"),
(UserPortfolioPermissionChoices.EDIT_MEMBERS.value, "Create and edit members")
],
widget=forms.RadioSelect,
required=False,
error_messages={
"required": "Admin member permission is required",
},
)
domain_request_permissions_member = forms.ChoiceField(
label="Select permission",
choices=[
(UserPortfolioPermissionChoices.VIEW_MEMBERS.value, "View all members"),
(UserPortfolioPermissionChoices.EDIT_MEMBERS.value, "Create and edit members")
],
widget=forms.RadioSelect,
required=False,
error_messages={
"required": "Basic member permission is required",
},
)
# this form dynamically shows/hides some fields, depending on what
# was selected prior. This toggles which field is required or not.
ROLE_REQUIRED_FIELDS = {
UserPortfolioRoleChoices.ORGANIZATION_ADMIN: [
"domain_request_permissions_admin",
"member_permissions_admin",
],
UserPortfolioRoleChoices.ORGANIZATION_MEMBER: [
"domain_request_permissions_member",
],
}
def _map_instance_to_form(self, instance):
"""Maps model instance data to form fields"""
if not instance:
return {}
mapped_data = {}
# Map roles with priority for admin
if instance.roles:
if UserPortfolioRoleChoices.ORGANIZATION_ADMIN.value in instance.roles:
mapped_data['role'] = UserPortfolioRoleChoices.ORGANIZATION_ADMIN.value
else:
mapped_data['role'] = UserPortfolioRoleChoices.ORGANIZATION_MEMBER.value
perms = UserPortfolioPermission.get_portfolio_permissions(instance.roles, instance.additional_permissions)
# Map permissions with priority for edit permissions
if perms:
if UserPortfolioPermissionChoices.EDIT_REQUESTS.value in perms:
mapped_data['domain_request_permissions_admin'] = UserPortfolioPermissionChoices.EDIT_REQUESTS.value
elif UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS.value in perms:
mapped_data['domain_request_permissions_admin'] = UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS.value
if UserPortfolioPermissionChoices.EDIT_MEMBERS.value in perms:
mapped_data['member_permissions_admin'] = UserPortfolioPermissionChoices.EDIT_MEMBERS.value
elif UserPortfolioPermissionChoices.VIEW_MEMBERS.value in perms:
mapped_data['member_permissions_admin'] = UserPortfolioPermissionChoices.VIEW_MEMBERS.value
return mapped_data
def _map_form_to_instance(self, instance):
"""Maps form data to model instance"""
if not self.is_valid():
return
role = self.cleaned_data.get("role")
domain_request_permissions_member = self.cleaned_data.get("domain_request_permissions_member")
domain_request_permissions_admin = self.cleaned_data.get('domain_request_permissions_admin')
member_permissions_admin = self.cleaned_data.get('member_permissions_admin')
instance.roles = [role]
additional_permissions = []
if domain_request_permissions_member:
additional_permissions.append(domain_request_permissions_member)
elif domain_request_permissions_admin:
additional_permissions.append(domain_request_permissions_admin)
if member_permissions_admin:
additional_permissions.append(member_permissions_admin)
instance.additional_permissions = additional_permissions
return instance
def clean(self):
cleaned_data = super().clean()
role = cleaned_data.get("role")
# Get required fields for the selected role.
# Then validate all required fields for the role.
required_fields = self.ROLE_REQUIRED_FIELDS.get(role, [])
for field_name in required_fields:
if not cleaned_data.get(field_name):
self.add_error(
field_name,
self.fields.get(field_name).error_messages.get("required")
)
return cleaned_data
class PortfolioMemberForm(BasePortfolioMemberForm):
"""
Form for updating a portfolio member.
"""
roles = forms.MultipleChoiceField(
choices=UserPortfolioRoleChoices.choices,
widget=forms.SelectMultiple(attrs={"class": "usa-select"}),
required=False,
label="Roles",
)
additional_permissions = forms.MultipleChoiceField(
choices=UserPortfolioPermissionChoices.choices,
widget=forms.SelectMultiple(attrs={"class": "usa-select"}),
required=False,
label="Additional Permissions",
)
class Meta:
model = UserPortfolioPermission
fields = [
"roles",
"additional_permissions",
]
def __init__(self, *args, instance=None, **kwargs):
super().__init__(*args, **kwargs)
self.fields['role'].descriptions = {
"organization_admin": UserPortfolioRoleChoices.get_role_description(UserPortfolioRoleChoices.ORGANIZATION_ADMIN),
"organization_member": UserPortfolioRoleChoices.get_role_description(UserPortfolioRoleChoices.ORGANIZATION_MEMBER)
}
self.instance = instance
self.initial = self._map_instance_to_form(self.instance)
def save(self):
"""Save form data to instance"""
if not self.instance:
self.instance = self.Meta.model()
self._map_form_to_instance(self.instance)
self.instance.save()
return self.instance
class PortfolioInvitedMemberForm(forms.ModelForm):
class PortfolioInvitedMemberForm(BasePortfolioMemberForm):
"""
Form for updating a portfolio invited member.
"""
roles = forms.MultipleChoiceField(
choices=UserPortfolioRoleChoices.choices,
widget=forms.SelectMultiple(attrs={"class": "usa-select"}),
required=False,
label="Roles",
)
additional_permissions = forms.MultipleChoiceField(
choices=UserPortfolioPermissionChoices.choices,
widget=forms.SelectMultiple(attrs={"class": "usa-select"}),
required=False,
label="Additional Permissions",
)
class Meta:
model = PortfolioInvitation
fields = [

View file

@ -17,6 +17,23 @@ class UserPortfolioRoleChoices(models.TextChoices):
@classmethod
def get_user_portfolio_role_label(cls, user_portfolio_role):
return cls(user_portfolio_role).label if user_portfolio_role else None
@classmethod
def get_role_description(cls, user_portfolio_role):
"""Returns a detailed description for a given role."""
descriptions = {
cls.ORGANIZATION_ADMIN: (
"Grants this member access to the organization-wide information "
"on domains, domain requests, and members. Domain management can be assigned separately."
),
cls.ORGANIZATION_MEMBER: (
"Grants this member access to the organization. They can be given extra permissions to view all "
"organization domain requests and submit domain requests on behalf of the organization. Basic access "
"members cant view all members of an organization or manage them. "
"Domain management can be assigned separately."
)
}
return descriptions.get(user_portfolio_role)
class UserPortfolioPermissionChoices(models.TextChoices):

View file

@ -1,3 +1,5 @@
{% load static custom_filters %}
<div class="{{ uswds_input_class }}">
{% for group, options, index in widget.optgroups %}
{% if group %}<div><label>{{ group }}</label>{% endif %}
@ -13,7 +15,17 @@
<label
class="{{ uswds_input_class }}__label{% if label_classes %} {{ label_classes }}{% endif %}"
for="{{ option.attrs.id }}"
>{{ option.label }}</label>
>
{{ option.label }}
{% comment %} Add a description on each, if available {% endcomment %}
{% if field and field.field and field.field.descriptions %}
{% with description=field.field.descriptions|get_dict_value:option.value %}
{% if description %}
<p class="margin-0 margin-top-2">{{ description }}</p>
{% endif %}
{% endwith %}
{% endif %}
</label>
{% endfor %}
{% if group %}</div>{% endif %}
{% endfor %}

View file

@ -10,12 +10,6 @@
{% block portfolio_content %}
<!-- Form mesages -->
{% include "includes/form_errors.html" with form=form %}
{% block messages %}
{% include "includes/form_messages.html" %}
{% endblock messages%}
<!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list">
@ -33,9 +27,7 @@
</nav>
<!-- Page header -->
{% block new_member_header %}
<h1>Member access and permissions</h1>
{% endblock new_member_header %}
{% include "includes/required_fields.html" %}
@ -45,7 +37,6 @@
<legend>
<h2>Member email</h2>
</legend>
{% comment %} TODO should default to name {% endcomment %}
<p>
{% if member %}
{{ member.email }}
@ -64,24 +55,15 @@
<em>Select the level of access for this member. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
<div class="usa-radio">
{% for radio in form.member_access_level %}
{{ radio.tag }}
<label class="usa-radio__label usa-legend" for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
<p class="margin-0 margin-top-2">
{% if radio.choice_label == "Admin Access" %}
Grants this member access to the organization-wide information on domains, domain requests, and members. Domain management can be assigned separately.
{% else %}
Grants this member access to the organization. They can be given extra permissions to view all organization domain requests and submit domain requests on behalf of the organization. Basic access members cant view all members of an organization or manage them. Domain management can be assigned separately.
{% endif %}
</p>
</label>
{% endfor %}
</div>
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
{% input_with_errors form.role %}
{% endwith %}
{% comment %} {% if radio.value == "organization_admin" %}
Grants this member access to the organization-wide information on domains, domain requests, and members. Domain management can be assigned separately.
{% elif radio.value == "organization_member" %}
Grants this member access to the organization. They can be given extra permissions to view all organization domain requests and submit domain requests on behalf of the organization. Basic access members cant view all members of an organization or manage them. Domain management can be assigned separately.
{% endif %} {% endcomment %}
</fieldset>
<!-- Admin access form -->
@ -93,7 +75,7 @@
text-primary-dark
margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.admin_org_domain_request_permissions %}
{% input_with_errors form.domain_request_permissions_admin %}
{% endwith %}
<h3 class="summary-item__title
@ -101,7 +83,7 @@
margin-bottom-0
margin-top-3">Organization members</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.admin_org_members_permissions %}
{% input_with_errors form.member_permissions_admin %}
{% endwith %}
</div>
@ -112,7 +94,7 @@
<h3 class="margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
{% input_with_errors form.basic_org_domain_request_permissions %}
{% input_with_errors form.domain_request_permissions_member %}
{% endwith %}
</div>
@ -123,17 +105,11 @@
href="{% url 'members' %}"
class="usa-button usa-button--outline"
name="btn-cancel-click"
aria-label="Cancel adding new member"
>Cancel
</a>
<a
id="invite_member_trigger"
href="#invite-member-modal"
class="usa-button usa-button--outline margin-top-1 display-none"
aria-controls="invite-member-modal"
data-open-modal
>Trigger invite member modal</a>
<button id="invite_new_member_submit" type="submit" class="usa-button">Invite Member</button>
aria-label="Cancel editing member"
>
Cancel
</a>
<button type="submit" class="usa-button">Update Member</button>
</div>
</form>

View file

@ -282,3 +282,11 @@ def display_requesting_entity(domain_request):
)
return display
@register.filter
def get_dict_value(dictionary, key):
"""Get a value from a dictionary. Returns a string on empty."""
if isinstance(dictionary, dict):
return dictionary.get(key, "")
return ""