Skip to content

Commit

Permalink
Merge pull request #2499 from cisagov/rjm/2377-portfolio-org-page
Browse files Browse the repository at this point in the history
Issues #2377 #2501: Portfolio org page - [RJM]
  • Loading branch information
rachidatecs authored Jul 30, 2024
2 parents 44bd242 + 5d5d931 commit 6637c0f
Show file tree
Hide file tree
Showing 8 changed files with 286 additions and 20 deletions.
3 changes: 3 additions & 0 deletions src/registrar/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
DomainDsdataFormset,
DomainDsdataForm,
)
from .portfolio import (
PortfolioOrgAddressForm,
)
2 changes: 1 addition & 1 deletion src/registrar/forms/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ class Meta:
# 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 = ["organization_name", "address_line1", "city", "zipcode"]
required = ["organization_name", "address_line1", "city", "state_territory", "zipcode"]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
69 changes: 69 additions & 0 deletions src/registrar/forms/portfolio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""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)
5 changes: 0 additions & 5 deletions src/registrar/models/portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@
from .utility.time_stamped_model import TimeStampedModel


# def get_default_federal_agency():
# """returns non-federal agency"""
# return FederalAgency.objects.filter(agency="Non-Federal Agency").first()


class Portfolio(TimeStampedModel):
"""
Portfolio is used for organizing domains/domain-requests into
Expand Down
58 changes: 57 additions & 1 deletion src/registrar/templates/portfolio_organization.html
Original file line number Diff line number Diff line change
@@ -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 %}
<h1>Organization</h1>
<div class="grid-row grid-gap">
<div class="tablet:grid-col-3">
<p class="font-body-md margin-top-0 margin-bottom-2
text-primary-darker text-semibold"
>
<span class="usa-sr-only"> Portfolio name:</span> {{ portfolio }}
</p>

{% include 'portfolio_organization_sidebar.html' %}
</div>

<div class="tablet:grid-col-9">

<h1>Organization</h1>

<p>The name of your federal agency will be publicly listed as the domain registrant.</p>

<p>
The federal agency for your organization can’t be updated here.
To suggest an update, email <a href="mailto:[email protected]" class="usa-link">[email protected]</a>.
</p>

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

{% include "includes/required_fields.html" %}

<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
{% csrf_token %}

<p>
<strong class="text-primary display-block margin-bottom-1">Federal agency</strong>
{{ portfolio }}
</p>

{% 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 %}

<button
type="submit"
class="usa-button"
>
Save
</button>
</form>
</div>
</div>
{% endblock %}
23 changes: 23 additions & 0 deletions src/registrar/templates/portfolio_organization_sidebar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% load static url_helpers %}

<div class="margin-bottom-4 tablet:margin-bottom-0">
<nav aria-label="Domain sections">
<ul class="usa-sidenav">
<li class="usa-sidenav__item">
{% url 'portfolio-organization' portfolio_id=portfolio.id as url %}
<a href="{{ url }}"
{% if request.path == url %}class="usa-current"{% endif %}
>
Organization
</a>
</li>

<li class="usa-sidenav__item">
<a href="#"
>
Senior official
</a>
</li>
</ul>
</nav>
</div>
70 changes: 69 additions & 1 deletion src/registrar/tests/test_views_portfolio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.urls import reverse
from api.tests.common import less_console_noise_decorator
from registrar.config import settings
from registrar.models.portfolio import Portfolio
from django_webtest import WebTest # type: ignore
from registrar.models import (
Expand All @@ -17,7 +18,7 @@
logger = logging.getLogger(__name__)


class TestPortfolioViews(WebTest):
class TestPortfolio(WebTest):
def setUp(self):
super().setUp()
self.user = create_test_user()
Expand Down Expand Up @@ -192,3 +193,70 @@ def test_navigation_links_hidden_when_user_not_have_permission(self):
self.assertNotContains(
portfolio_page, reverse("portfolio-domain-requests", kwargs={"portfolio_id": self.portfolio.pk})
)


class TestPortfolioOrganization(TestPortfolio):

def test_portfolio_org_name(self):
"""Can load portfolio's org name page."""
with override_flag("organization_feature", active=True):
self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()

page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
self.assertContains(
page, "The name of your federal agency will be publicly listed as the domain registrant."
)

def test_domain_org_name_address_content(self):
"""Org name and address information appears on the page."""
with override_flag("organization_feature", active=True):
self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()

self.portfolio.organization_name = "Hotel California"
self.portfolio.save()
page = self.app.get(reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk}))
# Once in the sidenav, once in the main nav, once in the form
self.assertContains(page, "Hotel California", count=3)

def test_domain_org_name_address_form(self):
"""Submitting changes works on the org name address page."""
with override_flag("organization_feature", active=True):
self.app.set_user(self.user.username)
self.user.portfolio = self.portfolio
self.user.portfolio_additional_permissions = [
User.UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
User.UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
]
self.user.save()
self.user.refresh_from_db()

self.portfolio.address_line1 = "1600 Penn Ave"
self.portfolio.save()
portfolio_org_name_page = self.app.get(
reverse("portfolio-organization", kwargs={"portfolio_id": self.portfolio.pk})
)
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]

portfolio_org_name_page.form["address_line1"] = "6 Downing st"
portfolio_org_name_page.form["city"] = "London"

self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
success_result_page = portfolio_org_name_page.form.submit()
self.assertEqual(success_result_page.status_code, 200)

self.assertContains(success_result_page, "6 Downing st")
self.assertContains(success_result_page, "London")
76 changes: 64 additions & 12 deletions src/registrar/views/portfolios.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -7,6 +11,10 @@
)
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):
Expand Down Expand Up @@ -42,17 +50,61 @@ def get(self, request, portfolio_id):
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"

def get(self, request, portfolio_id):
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

return render(request, "portfolio_organization.html", context)
form_class = PortfolioOrgAddressForm
context_object_name = "portfolio"

def get_context_data(self, **kwargs):
"""Add additional context data to the template."""
context = super().get_context_data(**kwargs)
# no need to add portfolio to request context here
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

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

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})

0 comments on commit 6637c0f

Please sign in to comment.