Skip to content

Commit

Permalink
Merge pull request #2895 from cisagov/za/2760-portfolio-domain-reques…
Browse files Browse the repository at this point in the history
…t-entry-point-2

#2760: Portfolio domain request entry point - [ZA]
  • Loading branch information
zandercymatics authored Oct 9, 2024
2 parents f299dc8 + 2da39e9 commit 3db0388
Show file tree
Hide file tree
Showing 14 changed files with 439 additions and 147 deletions.
12 changes: 10 additions & 2 deletions src/registrar/config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
get_action_needed_email_for_user_json,
)

from registrar.views.domain_request import Step
from registrar.views.domain_request import Step, PortfolioDomainRequestStep
from registrar.views.transfer_user import TransferUserView
from registrar.views.utility import always_404
from api.views import available, rdap, get_current_federal, get_current_full
Expand Down Expand Up @@ -61,6 +61,9 @@
(Step.ADDITIONAL_DETAILS, views.AdditionalDetails),
(Step.REQUIREMENTS, views.Requirements),
(Step.REVIEW, views.Review),
# Portfolio steps
(PortfolioDomainRequestStep.REQUESTING_ENTITY, views.RequestingEntity),
(PortfolioDomainRequestStep.ADDITIONAL_DETAILS, views.PortfolioAdditionalDetails),
]:
domain_request_urls.append(path(f"{step}/", view.as_view(), name=step))

Expand Down Expand Up @@ -184,7 +187,12 @@
name="export_data_type_requests",
),
path(
"domain-request/<id>/edit/",
"reports/export_data_type_requests/",
ExportDataTypeRequests.as_view(),
name="export_data_type_requests",
),
path(
"domain-request/<int:id>/edit/",
views.DomainRequestWizard.as_view(),
name=views.DomainRequestWizard.EDIT_URL_NAME,
),
Expand Down
7 changes: 7 additions & 0 deletions src/registrar/forms/domain_request_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
logger = logging.getLogger(__name__)


class RequestingEntityForm(RegistrarForm):
organization_name = forms.CharField(
label="Organization name",
error_messages={"required": "Enter the name of your organization."},
)


class OrganizationTypeForm(RegistrarForm):
generic_org_type = forms.ChoiceField(
# use the long names in the domain request form
Expand Down
6 changes: 3 additions & 3 deletions src/registrar/forms/utility/wizard_form_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,11 +279,11 @@ def get_initial_value(self):
return initial_value


def request_step_list(request_wizard):
def request_step_list(request_wizard, step_enum):
"""Dynamically generated list of steps in the form wizard."""
step_list = []
for step in request_wizard.StepEnum:
condition = request_wizard.WIZARD_CONDITIONS.get(step, True)
for step in step_enum:
condition = request_wizard.wizard_conditions.get(step, True)
if callable(condition):
condition = condition(request_wizard)
if condition:
Expand Down
4 changes: 3 additions & 1 deletion src/registrar/templates/domain_request_dotgov_domain.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

<p>Names that <em>uniquely apply to your organization</em> are likely to be approved over names that could also apply to other organizations.
{% if not is_federal %}In most instances, this requires including your state’s two-letter abbreviation.{% endif %}</p>


{% if not portfolio %}
<p>Requests for your organization’s initials or an abbreviated name might not be approved, but we encourage you to request the name you want.</p>
{% endif %}

<p>Note that <strong>only federal agencies can request generic terms</strong> like
vote.gov.</p>
Expand Down
4 changes: 4 additions & 0 deletions src/registrar/templates/domain_request_intro.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@

<h1>You’re about to start your .gov domain request.</h1>
<p>You don’t have to complete the process in one session. You can save what you enter and come back to it when you’re ready.</p>
{% if portfolio %}
<p>We’ll use the information you provide to verify your domain request meets our guidelines.</p>
{% else %}
<p>We’ll use the information you provide to verify your organization’s eligibility for a .gov domain. We’ll also verify that the domain you request meets our guidelines.</p>
{% endif %}
<h2>Time to complete the form</h2>
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
completing your domain request might take around 15 minutes.</p>
Expand Down
16 changes: 16 additions & 0 deletions src/registrar/templates/domain_request_requesting_entity.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% extends 'domain_request_form.html' %}
{% load field_helpers url_helpers %}

{% block form_instructions %}
<p>🛸🛸🛸🛸 Placeholder content 🛸🛸🛸🛸</p>
{% endblock %}

{% block form_fields %}
<fieldset class="usa-fieldset">
<legend>
<h2>What is the name of your space vessel?</h2>
</legend>

{% input_with_errors forms.0.organization_name %}
</fieldset>
{% endblock %}
6 changes: 5 additions & 1 deletion src/registrar/templates/domain_request_review.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ <h1> Review and submit your domain request </h1>
{% endblock %}

{% block form_fields %}
{% include "includes/request_review_steps.html" with is_editable=True %}
{% if portfolio %}
{% include "includes/portfolio_request_review_steps.html" with is_editable=True %}
{% else %}
{% include "includes/request_review_steps.html" with is_editable=True %}
{% endif %}
{% endblock %}
6 changes: 4 additions & 2 deletions src/registrar/templates/includes/header_extended.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
</ul>
</div>
<ul class="usa-nav__primary usa-accordion">
{% if not hide_domains %}
<li class="usa-nav__primary-item">
{% if has_any_domains_portfolio_permission %}
{% url 'domains' as url %}
Expand All @@ -44,13 +45,14 @@
Domains
</a>
</li>
{% endif %}
<!-- <li class="usa-nav__primary-item">
<a href="#" class="usa-nav-link">
Domain groups
</a>
</li> -->
{% if has_organization_requests_flag %}

{% if has_organization_requests_flag and not hide_requests %}
<li class="usa-nav__primary-item">
<!-- user has one of the view permissions plus the edit permission, show the dropdown -->
{% if has_edit_request_portfolio_permission %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
{% endif %}

{% if step == Step.REQUESTING_ENTITY %}

{% if domain_request.organization_name %}
{% with title=form_titles|get_item:step value=domain_request %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url address='true' %}
Expand Down Expand Up @@ -54,32 +53,8 @@ <h3 class="register-form-review-header">Alternative domains</h3>
{% endif %}

{% if step == Step.ADDITIONAL_DETAILS %}
{% with title=form_titles|get_item:step %}
{% if domain_request.has_additional_details %}
{% include "includes/summary_item.html" with title="Additional Details" value=" " heading_level=heading_level editable=is_editable edit_link=domain_request_url %}
<h3 class="register-form-review-header">CISA Regional Representative</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if domain_request.cisa_representative_first_name %}
<li>{{domain_request.cisa_representative_first_name}} {{domain_request.cisa_representative_last_name}}</li>
{% if domain_request.cisa_representative_email %}
<li>{{domain_request.cisa_representative_email}}</li>
{% endif %}
{% else %}
No
{% endif %}
</ul>

<h3 class="register-form-review-header">Anything else</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if domain_request.anything_else %}
{{domain_request.anything_else}}
{% else %}
No
{% endif %}
</ul>
{% else %}
{% include "includes/summary_item.html" with title="Additional Details" value="<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe heading_level=heading_level editable=is_editable edit_link=domain_request_url %}
{% endif %}
{% with title=form_titles|get_item:step value=domain_request.anything_else|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %}
{% endwith %}
{% endif %}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{% extends 'domain_request_form.html' %}
{% load static field_helpers %}

{% block form_required_fields_help_text %}
{% include "includes/required_fields.html" %}
{% endblock %}

{% block form_fields %}

<fieldset class="usa-fieldset margin-top-2">
<legend>
<h2>Is there anything else you’d like us to know about your domain request?</h2>
</legend>
</fieldset>

<div class="margin-top-3" id="anything-else">
<p>Provide details below. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></p>
{% with attr_maxlength=2000 add_label_class="usa-sr-only" %}
{% input_with_errors forms.0.anything_else %}
{% endwith %}
</div>
{% endblock %}
154 changes: 151 additions & 3 deletions src/registrar/tests/test_views_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def setUp(self):
super().setUp()
self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
self.app.set_user(self.user.username)
self.TITLES = DomainRequestWizard.TITLES
self.TITLES = DomainRequestWizard.REGULAR_TITLES

def tearDown(self):
super().tearDown()
Expand Down Expand Up @@ -2741,6 +2741,66 @@ def test_submit_modal_no_domain_text_fallback(self):
self.assertContains(review_page, "toggle-submit-domain-request")
self.assertContains(review_page, "Your request form is incomplete")

@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
def test_portfolio_user_missing_edit_permissions(self):
"""Tests that a portfolio user without edit request permissions cannot edit or add new requests"""
portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Test Portfolio")
portfolio_perm, _ = UserPortfolioPermission.objects.get_or_create(
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
)
# This user should be forbidden from creating new domain requests
intro_page = self.app.get(reverse("domain-request:"), expect_errors=True)
self.assertEqual(intro_page.status_code, 403)

# This user should also be forbidden from editing existing ones
domain_request = completed_domain_request(user=self.user)
edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk}), expect_errors=True)
self.assertEqual(edit_page.status_code, 403)

# Cleanup
portfolio_perm.delete()
portfolio.delete()

@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
def test_portfolio_user_with_edit_permissions(self):
"""Tests that a portfolio user with edit request permissions can edit and add new requests"""
portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Test Portfolio")
portfolio_perm, _ = UserPortfolioPermission.objects.get_or_create(
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
)

# This user should be allowed to create new domain requests
intro_page = self.app.get(reverse("domain-request:"))
self.assertEqual(intro_page.status_code, 200)

# This user should also be allowed to edit existing ones
domain_request = completed_domain_request(user=self.user)
edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})).follow()
self.assertEqual(edit_page.status_code, 200)

# Cleanup
DomainRequest.objects.all().delete()
portfolio_perm.delete()
portfolio.delete()

def test_non_creator_access(self):
"""Tests that a user cannot edit a domain request they didn't create"""
p = "password"
other_user = User.objects.create_user(username="other_user", password=p)
domain_request = completed_domain_request(user=other_user)

edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk}), expect_errors=True)
self.assertEqual(edit_page.status_code, 403)

def test_creator_access(self):
"""Tests that a user can edit a domain request they created"""
domain_request = completed_domain_request(user=self.user)

edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})).follow()
self.assertEqual(edit_page.status_code, 200)


class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
def setUp(self):
Expand Down Expand Up @@ -2903,7 +2963,7 @@ def test_approved_domain_request_not_in_active_requests(self):
self.assertNotContains(home_page, "city.gov")


class TestWizardUnlockingSteps(TestWithUser, WebTest):
class TestDomainRequestWizard(TestWithUser, WebTest):
def setUp(self):
super().setUp()
self.app.set_user(self.user.username)
Expand Down Expand Up @@ -3025,6 +3085,94 @@ def test_unlocked_steps_partial_domain_request(self):
else:
self.fail(f"Expected a redirect, but got a different response: {response}")

@less_console_noise_decorator
@override_flag("organization_feature", active=True)
@override_flag("organization_requests", active=True)
def test_wizard_steps_portfolio(self):
"""
Tests the behavior of the domain request wizard for portfolios.
Ensures that:
- The user can access the organization page.
- The expected number of steps are locked/unlocked (implicit test for expected steps).
- The user lands on the "Requesting entity" page
- The user does not see the Domain and Domain requests buttons
"""

# This should unlock 4 steps by default.
# Purpose, .gov domain, current websites, and requirements for operating
domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED,
user=self.user,
)
domain_request.anything_else = None
domain_request.save()

federal_agency = FederalAgency.objects.get(agency="Non-Federal Agency")
# Add a portfolio
portfolio = Portfolio.objects.create(
creator=self.user,
organization_name="test portfolio",
federal_agency=federal_agency,
)

user_portfolio_permission = UserPortfolioPermission.objects.create(
user=self.user,
portfolio=portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)

response = self.app.get(f"/domain-request/{domain_request.id}/edit/")
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)

# Check if the response is a redirect
if response.status_code == 302:
# Follow the redirect manually
try:
detail_page = response.follow()

self.wizard.get_context_data()
except Exception as err:
# Handle any potential errors while following the redirect
self.fail(f"Error following the redirect {err}")

# Now 'detail_page' contains the response after following the redirect
self.assertEqual(detail_page.status_code, 200)

# Assert that we're on the organization page
self.assertContains(detail_page, portfolio.organization_name)

# We should only see one unlocked step
self.assertContains(detail_page, "#check_circle", count=4)

# One pages should still be locked (additional details)
self.assertContains(detail_page, "#lock", 1)

# The current option should be selected
self.assertContains(detail_page, "usa-current", count=1)

# We default to the requesting entity page
expected_url = reverse("domain-request:portfolio_requesting_entity")
# This returns the entire url, thus "in"
self.assertIn(expected_url, detail_page.request.url)

# We shouldn't show the "domains" and "domain requests" buttons
# on this page.
self.assertNotContains(detail_page, "Domains")
self.assertNotContains(detail_page, "Domain requests")
else:
self.fail(f"Expected a redirect, but got a different response: {response}")

# Data cleanup
user_portfolio_permission.delete()
portfolio.delete()
federal_agency.delete()
domain_request.delete()


class TestPortfolioDomainRequestViewonly(TestWithUser, WebTest):

Expand All @@ -3036,7 +3184,7 @@ def setUp(self):
super().setUp()
self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
self.app.set_user(self.user.username)
self.TITLES = DomainRequestWizard.TITLES
self.TITLES = DomainRequestWizard.REGULAR_TITLES

def tearDown(self):
super().tearDown()
Expand Down
Loading

0 comments on commit 3db0388

Please sign in to comment.