manage.get.gov/src/registrar/forms/portfolio.py
2024-12-09 15:03:39 -07:00

390 lines
14 KiB
Python

"""Forms for portfolio."""
import logging
from django import forms
from django.core.validators import RegexValidator
from django.core.validators import MaxLengthValidator
from registrar.models import (
PortfolioInvitation,
UserPortfolioPermission,
DomainInformation,
Portfolio,
SeniorOfficial,
User,
)
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
logger = logging.getLogger(__name__)
class PortfolioOrgAddressForm(forms.ModelForm):
"""Form for updating the portfolio org mailing address."""
zipcode = forms.CharField(
label="Zip code",
validators=[
RegexValidator(
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
message="Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789.",
)
],
error_messages={
"required": "Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789.",
},
)
class Meta:
model = Portfolio
fields = [
"address_line1",
"address_line2",
"city",
"state_territory",
"zipcode",
# "urbanization",
]
error_messages = {
"address_line1": {"required": "Enter the street address of your organization."},
"city": {"required": "Enter the city where your organization is located."},
"state_territory": {
"required": "Select the state, territory, or military post where your organization is located."
},
"zipcode": {"required": "Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789."},
}
widgets = {
# We need to set the required attributed for State/territory
# because for this fields we are creating an individual
# instance of the Select. For the other fields we use the for loop to set
# the class's required attribute to true.
"address_line1": forms.TextInput,
"address_line2": forms.TextInput,
"city": forms.TextInput,
"state_territory": forms.Select(
attrs={
"required": True,
},
choices=DomainInformation.StateTerritoryChoices.choices,
),
# "urbanization": forms.TextInput,
}
# the database fields have blank=True so ModelForm doesn't create
# required fields by default. Use this list in __init__ to mark each
# of these fields as required
required = ["address_line1", "city", "state_territory", "zipcode"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name in self.required:
self.fields[field_name].required = True
self.fields["state_territory"].widget.attrs.pop("maxlength", None)
self.fields["zipcode"].widget.attrs.pop("maxlength", None)
class PortfolioSeniorOfficialForm(forms.ModelForm):
"""
Form for updating the portfolio senior official.
This form is readonly for now.
"""
JOIN = "senior_official"
full_name = forms.CharField(label="Full name", required=False)
class Meta:
model = SeniorOfficial
fields = [
"title",
"email",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.id:
self.fields["full_name"].initial = self.instance.get_formatted_name()
def clean(self):
"""Clean override to remove unused fields"""
cleaned_data = super().clean()
cleaned_data.pop("full_name", None)
return cleaned_data
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.
"""
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(BasePortfolioMemberForm):
"""
Form for updating a portfolio invited member.
"""
class Meta:
model = PortfolioInvitation
fields = [
"roles",
"additional_permissions",
]
class NewMemberForm(forms.ModelForm):
member_access_level = forms.ChoiceField(
label="Select permission",
choices=[("admin", "Admin Access"), ("basic", "Basic Access")],
widget=forms.RadioSelect(attrs={"class": "usa-radio__input usa-radio__input--tile"}),
required=True,
error_messages={
"required": "Member access level is required",
},
)
admin_org_domain_request_permissions = forms.ChoiceField(
label="Select permission",
choices=[("view_only", "View all requests"), ("view_and_create", "View all requests plus create requests")],
widget=forms.RadioSelect,
required=True,
error_messages={
"required": "Admin domain request permission is required",
},
)
admin_org_members_permissions = forms.ChoiceField(
label="Select permission",
choices=[("view_only", "View all members"), ("view_and_create", "View all members plus manage members")],
widget=forms.RadioSelect,
required=True,
error_messages={
"required": "Admin member permission is required",
},
)
basic_org_domain_request_permissions = forms.ChoiceField(
label="Select permission",
choices=[
("view_only", "View all requests"),
("view_and_create", "View all requests plus create requests"),
("no_access", "No access"),
],
widget=forms.RadioSelect,
required=True,
error_messages={
"required": "Basic member permission is required",
},
)
email = forms.EmailField(
label="Enter the email of the member you'd like to invite",
max_length=None,
error_messages={
"invalid": ("Enter an email address in the required format, like name@example.com."),
"required": ("Enter an email address in the required format, like name@example.com."),
},
validators=[
MaxLengthValidator(
320,
message="Response must be less than 320 characters.",
)
],
required=True,
)
class Meta:
model = User
fields = ["email"]
def clean(self):
cleaned_data = super().clean()
# Lowercase the value of the 'email' field
email_value = cleaned_data.get("email")
if email_value:
cleaned_data["email"] = email_value.lower()
##########################################
# TODO: future ticket
# (invite new member)
##########################################
# Check for an existing user (if there isn't any, send an invite)
# if email_value:
# try:
# existingUser = User.objects.get(email=email_value)
# except User.DoesNotExist:
# raise forms.ValidationError("User with this email does not exist.")
member_access_level = cleaned_data.get("member_access_level")
# Intercept the error messages so that we don't validate hidden inputs
if not member_access_level:
# If no member access level has been selected, delete error messages
# for all hidden inputs (which is everything except the e-mail input
# and member access selection)
for field in self.fields:
if field in self.errors and field != "email" and field != "member_access_level":
del self.errors[field]
return cleaned_data
basic_dom_req_error = "basic_org_domain_request_permissions"
admin_dom_req_error = "admin_org_domain_request_permissions"
admin_member_error = "admin_org_members_permissions"
if member_access_level == "admin" and basic_dom_req_error in self.errors:
# remove the error messages pertaining to basic permission inputs
del self.errors[basic_dom_req_error]
elif member_access_level == "basic":
# remove the error messages pertaining to admin permission inputs
if admin_dom_req_error in self.errors:
del self.errors[admin_dom_req_error]
if admin_member_error in self.errors:
del self.errors[admin_member_error]
return cleaned_data