diff --git a/src/registrar/forms/__init__.py b/src/registrar/forms/__init__.py index a0c71d48c..374b3102f 100644 --- a/src/registrar/forms/__init__.py +++ b/src/registrar/forms/__init__.py @@ -10,3 +10,6 @@ from .domain import ( DomainDsdataFormset, DomainDsdataForm, ) +from .portfolio import ( + PortfolioOrgAddressForm, +) diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py new file mode 100644 index 000000000..5e6443e38 --- /dev/null +++ b/src/registrar/forms/portfolio.py @@ -0,0 +1,105 @@ +"""Forms for portfolio.""" + +import logging +from django import forms +from django.core.validators import RegexValidator + +from ..models import DomainInformation, Portfolio + +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 zip code in the required format, 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." + }, + } + 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) + + # self.is_federal = self.instance.generic_org_type == DomainRequest.OrganizationChoices.FEDERAL + # self.is_tribal = self.instance.generic_org_type == DomainRequest.OrganizationChoices.TRIBAL + + # field_to_disable = None + # if self.is_federal: + # field_to_disable = "federal_agency" + # elif self.is_tribal: + # field_to_disable = "organization_name" + + # if field_to_disable is not None: + # DomainHelper.disable_field(self.fields[field_to_disable], disable_required=True) + + # def save(self, commit=True): + # """Override the save() method of the BaseModelForm.""" + # if self.has_changed(): + + # if self.is_federal and not self._field_unchanged("federal_agency"): + # raise ValueError("federal_agency cannot be modified when the generic_org_type is federal") + # elif self.is_tribal and not self._field_unchanged("organization_name"): + # raise ValueError("organization_name cannot be modified when the generic_org_type is tribal") + + # else: + # super().save() + + # def _field_unchanged(self, field_name) -> bool: + # """ + # Checks if a specified field has not changed between the old value + # and the new value. + + # The old value is grabbed from self.initial. + # The new value is grabbed from self.cleaned_data. + # """ + # old_value = self.initial.get(field_name, None) + # new_value = self.cleaned_data.get(field_name, None) + # return old_value == new_value \ No newline at end of file diff --git a/src/registrar/templates/portfolio_organization.html b/src/registrar/templates/portfolio_organization.html index c7eae7130..0dede3c32 100644 --- a/src/registrar/templates/portfolio_organization.html +++ b/src/registrar/templates/portfolio_organization.html @@ -1,8 +1,64 @@ {% extends 'portfolio_base.html' %} +{% load static field_helpers%} + +{% block title %}Organization mailing address | {{ portfolio.name }} | {% endblock %} {% load static %} {% block portfolio_content %} -

Organization

+
+
+

+ Portfolio name: {{ portfolio }} +

+ {% include 'portfolio_organization_sidebar.html' %} +
+ +
+ +

Organization

+ +

The name of your federal agency will be publicly listed as the domain registrant.

+ +

+ The federal agency for your organization can’t be updated here. + To suggest an update, email help@get.gov. +

+ + {% include "includes/form_errors.html" with form=form %} + + {% include "includes/required_fields.html" %} + +
+ {% csrf_token %} + +

+ Federal agency + {{ portfolio }} +

+ + {% input_with_errors form.address_line1 %} + + {% input_with_errors form.address_line2 %} + + {% input_with_errors form.city %} + + {% input_with_errors form.state_territory %} + + {% with add_class="usa-input--small" %} + {% input_with_errors form.zipcode %} + {% endwith %} + + +
+
+
{% endblock %} diff --git a/src/registrar/templates/portfolio_organization_sidebar.html b/src/registrar/templates/portfolio_organization_sidebar.html new file mode 100644 index 000000000..6199c2476 --- /dev/null +++ b/src/registrar/templates/portfolio_organization_sidebar.html @@ -0,0 +1,23 @@ +{% load static url_helpers %} + +
+ +
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index f693e2cc9..a3ff97b06 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -1,4 +1,8 @@ +import logging from django.shortcuts import get_object_or_404, render +from django.urls import reverse +from django.contrib import messages +from registrar.forms.portfolio import PortfolioOrgAddressForm from registrar.models.portfolio import Portfolio from registrar.views.utility.permission_views import ( PortfolioDomainRequestsPermissionView, @@ -7,6 +11,10 @@ from registrar.views.utility.permission_views import ( ) from waffle.decorators import flag_is_active from django.views.generic import View +from django.views.generic.edit import FormMixin + + +logger = logging.getLogger(__name__) class PortfolioDomainsView(PortfolioDomainsPermissionView, View): @@ -42,17 +50,60 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View): return render(request, "portfolio_requests.html", context) -class PortfolioOrganizationView(PortfolioBasePermissionView, View): +class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin): + """ + View to handle displaying and updating the portfolio's organization details. + """ + model = Portfolio template_name = "portfolio_organization.html" + form_class = PortfolioOrgAddressForm + context_object_name = "portfolio" - def get(self, request, portfolio_id): - context = {} + def get_context_data(self, **kwargs): + """Add additional context data to the template.""" + context = super().get_context_data(**kwargs) + context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature") + context["has_organization_feature_flag"] = flag_is_active(self.request, "organization_feature") + return context - if self.request.user.is_authenticated: - context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") - context["has_organization_feature_flag"] = flag_is_active(request, "organization_feature") - portfolio = get_object_or_404(Portfolio, id=portfolio_id) - context["portfolio"] = portfolio + def get_object(self, queryset=None): + """Get the portfolio object based on the URL parameter.""" + return get_object_or_404(Portfolio, id=self.kwargs.get("portfolio_id")) + + def get_form_kwargs(self): + """Include the instance in the form kwargs.""" + kwargs = super().get_form_kwargs() + kwargs['instance'] = self.get_object() + return kwargs - return render(request, "portfolio_organization.html", context) + def get(self, request, *args, **kwargs): + """Handle GET requests to display the form.""" + self.object = self.get_object() + form = self.get_form() + return self.render_to_response(self.get_context_data(form=form)) + + def post(self, request, *args, **kwargs): + """Handle POST requests to process form submission.""" + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def form_valid(self, form): + """Handle the case when the form is valid.""" + self.object = form.save(commit=False) + self.object.creator = self.request.user + self.object.save() + messages.success(self.request, "The organization information for this portfolio has been updated.") + return super().form_valid(form) + + def form_invalid(self, form): + """Handle the case when the form is invalid.""" + return self.render_to_response(self.get_context_data(form=form)) + + def get_success_url(self): + """Redirect to the overview page for the portfolio.""" + return reverse("portfolio-organization", kwargs={"portfolio_id": self.object.pk})