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 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. 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: class Meta:
model = UserPortfolioPermission model = UserPortfolioPermission
fields = [ fields = [
"roles", "roles",
"additional_permissions", "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. 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: class Meta:
model = PortfolioInvitation model = PortfolioInvitation
fields = [ fields = [

View file

@ -18,6 +18,23 @@ class UserPortfolioRoleChoices(models.TextChoices):
def get_user_portfolio_role_label(cls, user_portfolio_role): def get_user_portfolio_role_label(cls, user_portfolio_role):
return cls(user_portfolio_role).label if user_portfolio_role else None 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): class UserPortfolioPermissionChoices(models.TextChoices):
""" """ """ """

View file

@ -1,3 +1,5 @@
{% load static custom_filters %}
<div class="{{ uswds_input_class }}"> <div class="{{ uswds_input_class }}">
{% for group, options, index in widget.optgroups %} {% for group, options, index in widget.optgroups %}
{% if group %}<div><label>{{ group }}</label>{% endif %} {% if group %}<div><label>{{ group }}</label>{% endif %}
@ -13,7 +15,17 @@
<label <label
class="{{ uswds_input_class }}__label{% if label_classes %} {{ label_classes }}{% endif %}" class="{{ uswds_input_class }}__label{% if label_classes %} {{ label_classes }}{% endif %}"
for="{{ option.attrs.id }}" 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 %} {% endfor %}
{% if group %}</div>{% endif %} {% if group %}</div>{% endif %}
{% endfor %} {% endfor %}

View file

@ -10,12 +10,6 @@
{% block portfolio_content %} {% block portfolio_content %}
<!-- Form mesages -->
{% include "includes/form_errors.html" with form=form %}
{% block messages %}
{% include "includes/form_messages.html" %}
{% endblock messages%}
<!-- Navigation breadcrumbs --> <!-- Navigation breadcrumbs -->
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb"> <nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list"> <ol class="usa-breadcrumb__list">
@ -33,9 +27,7 @@
</nav> </nav>
<!-- Page header --> <!-- Page header -->
{% block new_member_header %}
<h1>Member access and permissions</h1> <h1>Member access and permissions</h1>
{% endblock new_member_header %}
{% include "includes/required_fields.html" %} {% include "includes/required_fields.html" %}
@ -45,7 +37,6 @@
<legend> <legend>
<h2>Member email</h2> <h2>Member email</h2>
</legend> </legend>
{% comment %} TODO should default to name {% endcomment %}
<p> <p>
{% if member %} {% if member %}
{{ member.email }} {{ 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> <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" %} {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
<div class="usa-radio"> {% input_with_errors form.role %}
{% 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>
{% endwith %} {% 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> </fieldset>
<!-- Admin access form --> <!-- Admin access form -->
@ -93,7 +75,7 @@
text-primary-dark text-primary-dark
margin-bottom-0">Organization domain requests</h3> margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %} {% 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 %} {% endwith %}
<h3 class="summary-item__title <h3 class="summary-item__title
@ -101,7 +83,7 @@
margin-bottom-0 margin-bottom-0
margin-top-3">Organization members</h3> margin-top-3">Organization members</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %} {% 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 %} {% endwith %}
</div> </div>
@ -112,7 +94,7 @@
<h3 class="margin-bottom-0">Organization domain requests</h3> <h3 class="margin-bottom-0">Organization domain requests</h3>
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %} {% 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 %} {% endwith %}
</div> </div>
@ -123,17 +105,11 @@
href="{% url 'members' %}" href="{% url 'members' %}"
class="usa-button usa-button--outline" class="usa-button usa-button--outline"
name="btn-cancel-click" name="btn-cancel-click"
aria-label="Cancel adding new member" aria-label="Cancel editing member"
>Cancel >
Cancel
</a> </a>
<a <button type="submit" class="usa-button">Update Member</button>
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>
</div> </div>
</form> </form>

View file

@ -282,3 +282,11 @@ def display_requesting_entity(domain_request):
) )
return display 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 ""