+{% endblock %}
+
+{% block form_fields %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/src/registrar/templates/domain_request_review.html b/src/registrar/templates/domain_request_review.html
index 03624d2ec..03cf31287 100644
--- a/src/registrar/templates/domain_request_review.html
+++ b/src/registrar/templates/domain_request_review.html
@@ -19,5 +19,9 @@
{% 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 %}
diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html
index 8f7dba9ac..1c4286baa 100644
--- a/src/registrar/templates/includes/portfolio_request_review_steps.html
+++ b/src/registrar/templates/includes/portfolio_request_review_steps.html
@@ -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' %}
diff --git a/src/registrar/utility/enums.py b/src/registrar/utility/enums.py
index b4fc5ed6f..f67fd3f61 100644
--- a/src/registrar/utility/enums.py
+++ b/src/registrar/utility/enums.py
@@ -75,9 +75,10 @@ class PortfolioDomainRequestStep(StrEnum):
"""
# Portfolio
- REQUESTING_ENTITY = "organization_name"
+ REQUESTING_ENTITY = "requesting_entity"
CURRENT_SITES = "current_sites"
DOTGOV_DOMAIN = "dotgov_domain"
PURPOSE = "purpose"
ADDITIONAL_DETAILS = "additional_details"
REQUIREMENTS = "requirements"
+ REVIEW = "review"
diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py
index b7462f300..8cf748274 100644
--- a/src/registrar/views/domain_request.py
+++ b/src/registrar/views/domain_request.py
@@ -43,9 +43,11 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
although not without consulting the base implementation, first.
"""
- StepEnum: Step = Step # type: ignore
+ StepEnum: Step | PortfolioDomainRequestStep = Step # type: ignore
template_name = ""
+ is_portfolio = False
+
# uniquely namespace the wizard in urls.py
# (this is not seen _in_ urls, only for Django's internal naming)
# NB: this is included here for reference. Do not change it without
@@ -188,8 +190,32 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
else:
return default
+ def mark_as_portfolio_wizard(self):
+ """Swaps the wizard over to the "portfolio" view"""
+ self.is_portfolio = True
+ self.StepEnum = PortfolioDomainRequestStep # type: ignore
+ self.TITLES = {
+ self.StepEnum.REQUESTING_ENTITY: _("Requesting entity"),
+ self.StepEnum.CURRENT_SITES: _("Current websites"),
+ self.StepEnum.DOTGOV_DOMAIN: _(".gov domain"),
+ self.StepEnum.PURPOSE: _("Purpose of your domain"),
+ self.StepEnum.ADDITIONAL_DETAILS: _("Additional details"),
+ self.StepEnum.REQUIREMENTS: _("Requirements for operating a .gov domain"),
+ self.StepEnum.REVIEW: _("Review and submit your domain request"),
+ }
+ self.WIZARD_CONDITIONS = {}
+
+ # Regenerate the steps helper
+ print("look, da fuq")
+ print(self.storage)
+ self.steps = StepsHelper(self)
+
def get(self, request, *args, **kwargs):
"""This method handles GET requests."""
+
+ if not self.is_portfolio and self.request.user.is_org_user(request):
+ self.mark_as_portfolio_wizard()
+
current_url = resolve(request.path_info).url_name
# if user visited via an "edit" url, associate the id of the
@@ -211,6 +237,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# intro page.
return render(request, "domain_request_intro.html", {})
else:
+ print(f"look at these steps: {self.steps}")
return self.goto(self.steps.first)
# refresh step_history to ensure we don't erroneously unlock unfinished
@@ -219,6 +246,9 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
context = self.get_context_data()
self.steps.current = current_url
context["forms"] = self.get_forms()
+ print(f"storage is: {self.storage}")
+ print(f"steps are: {self.steps}")
+ print(f"context is: {context}")
# if pending requests exist and user does not have approved domains,
# present message that domain request cannot be submitted
@@ -334,42 +364,63 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# and is used to determine how steps appear in the side nav.
# It is worth noting that any step assigned "false" here will be EXCLUDED
# from the list of "unlocked" steps.
-
- history_dict = {
- "generic_org_type": self.domain_request.generic_org_type is not None,
- "tribal_government": self.domain_request.tribe_name is not None,
- "organization_federal": self.domain_request.federal_type is not None,
- "organization_election": self.domain_request.is_election_board is not None,
- "organization_contact": (
- self.domain_request.federal_agency is not None
- or self.domain_request.organization_name is not None
- or self.domain_request.address_line1 is not None
- or self.domain_request.city is not None
- or self.domain_request.state_territory is not None
- or self.domain_request.zipcode is not None
- or self.domain_request.urbanization is not None
- ),
- "about_your_organization": self.domain_request.about_your_organization is not None,
- "senior_official": self.domain_request.senior_official is not None,
- "current_sites": (
- self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None
- ),
- "dotgov_domain": self.domain_request.requested_domain is not None,
- "purpose": self.domain_request.purpose is not None,
- "other_contacts": (
- self.domain_request.other_contacts.exists()
- or self.domain_request.no_other_contacts_rationale is not None
- ),
- "additional_details": (
- # Additional details is complete as long as "has anything else" and "has cisa rep" are not None
- (
- self.domain_request.has_anything_else_text is not None
- and self.domain_request.has_cisa_representative is not None
- )
- ),
- "requirements": self.domain_request.is_policy_acknowledged is not None,
- "review": self.domain_request.is_policy_acknowledged is not None,
- }
+ if self.is_portfolio:
+ history_dict = {
+ "requesting_entity": self.domain_request.is_policy_acknowledged is not None,
+ "current_sites": (
+ self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None
+ ),
+ "dotgov_domain": self.domain_request.requested_domain is not None,
+ "purpose": self.domain_request.purpose is not None,
+ "other_contacts": (
+ self.domain_request.other_contacts.exists()
+ or self.domain_request.no_other_contacts_rationale is not None
+ ),
+ "additional_details": (
+ # Additional details is complete as long as "has anything else" and "has cisa rep" are not None
+ (
+ self.domain_request.has_anything_else_text is not None
+ and self.domain_request.has_cisa_representative is not None
+ )
+ ),
+ "review": self.domain_request.is_policy_acknowledged is not None,
+ }
+ else:
+ history_dict = {
+ "generic_org_type": self.domain_request.generic_org_type is not None,
+ "tribal_government": self.domain_request.tribe_name is not None,
+ "organization_federal": self.domain_request.federal_type is not None,
+ "organization_election": self.domain_request.is_election_board is not None,
+ "organization_contact": (
+ self.domain_request.federal_agency is not None
+ or self.domain_request.organization_name is not None
+ or self.domain_request.address_line1 is not None
+ or self.domain_request.city is not None
+ or self.domain_request.state_territory is not None
+ or self.domain_request.zipcode is not None
+ or self.domain_request.urbanization is not None
+ ),
+ "about_your_organization": self.domain_request.about_your_organization is not None,
+ "senior_official": self.domain_request.senior_official is not None,
+ "current_sites": (
+ self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None
+ ),
+ "dotgov_domain": self.domain_request.requested_domain is not None,
+ "purpose": self.domain_request.purpose is not None,
+ "other_contacts": (
+ self.domain_request.other_contacts.exists()
+ or self.domain_request.no_other_contacts_rationale is not None
+ ),
+ "additional_details": (
+ # Additional details is complete as long as "has anything else" and "has cisa rep" are not None
+ (
+ self.domain_request.has_anything_else_text is not None
+ and self.domain_request.has_cisa_representative is not None
+ )
+ ),
+ "requirements": self.domain_request.is_policy_acknowledged is not None,
+ "review": self.domain_request.is_policy_acknowledged is not None,
+ }
return [key for key, value in history_dict.items() if value]
def get_context_data(self):
@@ -420,7 +471,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
return request_step_list(self)
def goto(self, step):
- if step == "generic_org_type":
+ if step == "generic_org_type" or step == "requesting_entity":
# We need to avoid creating a new domain request if the user
# clicks the back button
self.request.session["new_request"] = False
@@ -443,6 +494,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
def post(self, request, *args, **kwargs) -> HttpResponse:
"""This method handles POST requests."""
+ if not self.is_portfolio and self.request.user.is_org_user(request):
+ self.mark_as_portfolio_wizard()
# which button did the user press?
button: str = request.POST.get("submit_button", "")
@@ -456,7 +509,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
if self.request.session["new_request"] is True:
# This will trigger the domain_request getter into creating a new DomainRequest
del self.storage
-
+ print(f"what are the steps? {self.steps}")
return self.goto(self.steps.first)
# if accessing this class directly, redirect to the first step
@@ -518,7 +571,12 @@ class PortfolioDomainRequestWizard(DomainRequestWizard):
self.steps = StepsHelper(self)
self._domain_request = None # for caching
+# Portfolio pages
+class RequestingEntity(DomainRequestWizard):
+ template_name = "domain_request_requesting_entity.html"
+ forms = [forms.RequestingEntityForm]
+# Non-portfolio pages
class OrganizationType(DomainRequestWizard):
template_name = "domain_request_org_type.html"
forms = [forms.OrganizationTypeForm]
From 1f2793bfc14e86c1b669c12c70d125d12737a663 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:21:52 -0600
Subject: [PATCH 02/22] Update domain_request.py
---
src/registrar/views/domain_request.py | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py
index 8cf748274..dbc93e289 100644
--- a/src/registrar/views/domain_request.py
+++ b/src/registrar/views/domain_request.py
@@ -206,8 +206,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
self.WIZARD_CONDITIONS = {}
# Regenerate the steps helper
- print("look, da fuq")
- print(self.storage)
self.steps = StepsHelper(self)
def get(self, request, *args, **kwargs):
@@ -237,7 +235,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# intro page.
return render(request, "domain_request_intro.html", {})
else:
- print(f"look at these steps: {self.steps}")
return self.goto(self.steps.first)
# refresh step_history to ensure we don't erroneously unlock unfinished
@@ -246,9 +243,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
context = self.get_context_data()
self.steps.current = current_url
context["forms"] = self.get_forms()
- print(f"storage is: {self.storage}")
- print(f"steps are: {self.steps}")
- print(f"context is: {context}")
# if pending requests exist and user does not have approved domains,
# present message that domain request cannot be submitted
@@ -383,6 +377,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
and self.domain_request.has_cisa_representative is not None
)
),
+ "requirements": self.domain_request.is_policy_acknowledged is not None,
"review": self.domain_request.is_policy_acknowledged is not None,
}
else:
@@ -509,7 +504,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
if self.request.session["new_request"] is True:
# This will trigger the domain_request getter into creating a new DomainRequest
del self.storage
- print(f"what are the steps? {self.steps}")
return self.goto(self.steps.first)
# if accessing this class directly, redirect to the first step
@@ -756,7 +750,7 @@ class Review(DomainRequestWizard):
if DomainRequest._form_complete(self.domain_request, self.request) is False:
logger.warning("User arrived at review page with an incomplete form.")
context = super().get_context_data()
- context["Step"] = Step.__members__
+ context["Step"] = self.StepEnum.__members__
context["domain_request"] = self.domain_request
return context
From b08c7e1478d120e610305c2e5b849dcdb0b58764 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:39:43 -0600
Subject: [PATCH 03/22] Configure permissions correctly
---
src/registrar/config/urls.py | 2 +-
src/registrar/views/utility/mixins.py | 18 ++++++++++++++++++
2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index ba93d1dec..0f76596c5 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -175,7 +175,7 @@ urlpatterns = [
name="export_data_type_user",
),
path(
- "domain-request//edit/",
+ "domain-request//edit/",
views.DomainRequestWizard.as_view(),
name=views.DomainRequestWizard.EDIT_URL_NAME,
),
diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py
index d8c48e01e..74651ee6d 100644
--- a/src/registrar/views/utility/mixins.py
+++ b/src/registrar/views/utility/mixins.py
@@ -384,10 +384,28 @@ class DomainRequestWizardPermission(PermissionsLoginMixin):
The user is in self.request.user
"""
+ if not self.request.user.is_authenticated:
+ return False
+
# The user has an ineligible flag
if self.request.user.is_restricted():
return False
+ # user needs to be the creator of the domain request to edit it.
+ id = self.kwargs.get("id") if hasattr(self, "kwargs") else None
+ if not id:
+ domain_request_wizard = self.request.session.get("wizard_domain_request")
+ if domain_request_wizard:
+ id = domain_request_wizard.get("domain_request_id")
+
+ if not DomainRequest.objects.filter(creator=self.request.user, id=id).exists():
+ return False
+
+ if self.request.user.is_org_user(self.request):
+ portfolio = self.request.session.get("portfolio")
+ if not self.request.user.has_edit_request_portfolio_permission(portfolio):
+ return False
+
return True
From eef82382aecd6128fa8dfe351238fec12b7811c3 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 3 Oct 2024 12:27:51 -0600
Subject: [PATCH 04/22] Hide content in navbar
---
src/registrar/forms/domain_request_wizard.py | 8 +++-----
.../templates/domain_request_dotgov_domain.html | 4 +++-
.../templates/domain_request_intro.html | 4 ++++
.../domain_request_requesting_entity.html | 4 ++--
.../templates/includes/header_extended.html | 6 ++++--
src/registrar/views/domain_request.py | 17 ++++++++++++++---
src/registrar/views/utility/mixins.py | 16 ++++++++++------
7 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py
index c40184a20..6b160b14d 100644
--- a/src/registrar/forms/domain_request_wizard.py
+++ b/src/registrar/forms/domain_request_wizard.py
@@ -22,11 +22,9 @@ logger = logging.getLogger(__name__)
class RequestingEntityForm(RegistrarForm):
- is_policy_acknowledged = forms.BooleanField(
- label="I read and agree to the requirements for operating a .gov domain.",
- error_messages={
- "required": ("Check the box if you read and agree to the requirements for operating a .gov domain.")
- },
+ organization_name = forms.CharField(
+ label="Organization name",
+ error_messages={"required": "Enter the name of your organization."},
)
diff --git a/src/registrar/templates/domain_request_dotgov_domain.html b/src/registrar/templates/domain_request_dotgov_domain.html
index 5864cad29..764154254 100644
--- a/src/registrar/templates/domain_request_dotgov_domain.html
+++ b/src/registrar/templates/domain_request_dotgov_domain.html
@@ -12,8 +12,10 @@
Names that uniquely apply to your organization 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 %}
-
+
+ {% if not portfolio %}
Requests for your organization’s initials or an abbreviated name might not be approved, but we encourage you to request the name you want.
+ {% endif %}
Note that only federal agencies can request generic terms like
vote.gov.
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.
+ {% if portfolio %}
+
We’ll use the information you provide to verify your domain request meets our guidelines.
+ {% else %}
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.
diff --git a/src/registrar/templates/domain_request_requesting_entity.html b/src/registrar/templates/domain_request_requesting_entity.html
index 831c37984..375f7ce74 100644
--- a/src/registrar/templates/domain_request_requesting_entity.html
+++ b/src/registrar/templates/domain_request_requesting_entity.html
@@ -8,9 +8,9 @@
{% block form_fields %}
{% endblock %}
\ No newline at end of file
diff --git a/src/registrar/templates/includes/header_extended.html b/src/registrar/templates/includes/header_extended.html
index ab53dd5cf..c10245946 100644
--- a/src/registrar/templates/includes/header_extended.html
+++ b/src/registrar/templates/includes/header_extended.html
@@ -34,6 +34,7 @@
+ {% if not hide_domains %}
{% if has_any_domains_portfolio_permission %}
{% url 'domains' as url %}
@@ -44,13 +45,14 @@
Domains
+ {% endif %}
-
- {% if has_organization_requests_flag %}
+
+ {% if has_organization_requests_flag and not hide_requests %}
{% if has_edit_request_portfolio_permission %}
diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py
index dbc93e289..263e57be1 100644
--- a/src/registrar/views/domain_request.py
+++ b/src/registrar/views/domain_request.py
@@ -130,10 +130,16 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# If a user is creating a request, we assume that perms are handled upstream
if self.request.user.is_org_user(self.request):
+ portfolio = self.request.session.get("portfolio")
self._domain_request = DomainRequest.objects.create(
creator=self.request.user,
- portfolio=self.request.session.get("portfolio"),
+ portfolio=portfolio,
)
+
+ # TODO - is this needed?
+ if portfolio:
+ self._domain_request.generic_org_type = portfolio.organization_type
+ self._domain_request.save()
else:
self._domain_request = DomainRequest.objects.create(creator=self.request.user)
@@ -233,7 +239,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# Clear context so the prop getter won't create a request here.
# Creating a request will be handled in the post method for the
# intro page.
- return render(request, "domain_request_intro.html", {})
+ return render(request, "domain_request_intro.html", {"hide_requests": True, "hide_domains": True})
else:
return self.goto(self.steps.first)
@@ -360,7 +366,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# from the list of "unlocked" steps.
if self.is_portfolio:
history_dict = {
- "requesting_entity": self.domain_request.is_policy_acknowledged is not None,
+ "requesting_entity": self.domain_request.organization_name is not None,
"current_sites": (
self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None
),
@@ -459,6 +465,11 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
"user": self.request.user,
"requested_domain__name": requested_domain_name,
}
+
+ # Hides the requests and domains buttons in the navbar
+ context_stuff["hide_requests"] = self.is_portfolio
+ context_stuff["hide_domains"] = self.is_portfolio
+
return context_stuff
def get_step_list(self) -> list:
diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py
index 74651ee6d..3499c147c 100644
--- a/src/registrar/views/utility/mixins.py
+++ b/src/registrar/views/utility/mixins.py
@@ -391,6 +391,12 @@ class DomainRequestWizardPermission(PermissionsLoginMixin):
if self.request.user.is_restricted():
return False
+ # If the user is an org user and doesn't have add/edit perms, forbid this
+ if self.request.user.is_org_user(self.request):
+ portfolio = self.request.session.get("portfolio")
+ if not self.request.user.has_edit_request_portfolio_permission(portfolio):
+ return False
+
# user needs to be the creator of the domain request to edit it.
id = self.kwargs.get("id") if hasattr(self, "kwargs") else None
if not id:
@@ -398,12 +404,10 @@ class DomainRequestWizardPermission(PermissionsLoginMixin):
if domain_request_wizard:
id = domain_request_wizard.get("domain_request_id")
- if not DomainRequest.objects.filter(creator=self.request.user, id=id).exists():
- return False
-
- if self.request.user.is_org_user(self.request):
- portfolio = self.request.session.get("portfolio")
- if not self.request.user.has_edit_request_portfolio_permission(portfolio):
+ # If no id is provided, we can assume that the user is starting a new request.
+ # If one IS provided, check that they are the original creator of it.
+ if id:
+ if not DomainRequest.objects.filter(creator=self.request.user, id=id).exists():
return False
return True
From 4aa91ccc9cd1524641ec5bfecbe7fc0a3471685f Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 4 Oct 2024 09:24:28 -0600
Subject: [PATCH 05/22] Unit tests
---
src/registrar/config/urls.py | 7 -
.../domain_request_requesting_entity.html | 2 +-
src/registrar/tests/test_views_request.py | 152 +++++++++++++++++-
src/registrar/views/domain_request.py | 25 +--
4 files changed, 167 insertions(+), 19 deletions(-)
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index b25f6e0ea..e7f1883cd 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -60,15 +60,8 @@ for step, view in [
(Step.ADDITIONAL_DETAILS, views.AdditionalDetails),
(Step.REQUIREMENTS, views.Requirements),
(Step.REVIEW, views.Review),
-
# Portfolio steps
(PortfolioDomainRequestStep.REQUESTING_ENTITY, views.RequestingEntity),
- # (PortfolioDomainRequestStep.CURRENT_SITES, views.CurrentSites),
- # (PortfolioDomainRequestStep.DOTGOV_DOMAIN, views.DotgovDomain),
- # (PortfolioDomainRequestStep.PURPOSE, views.Purpose),
- # (PortfolioDomainRequestStep.ADDITIONAL_DETAILS, views.AdditionalDetails),
- # (PortfolioDomainRequestStep.REQUIREMENTS, views.Requirements),
- # (PortfolioDomainRequestStep.REVIEW, views.Review),
]:
domain_request_urls.append(path(f"{step}/", view.as_view(), name=step))
diff --git a/src/registrar/templates/domain_request_requesting_entity.html b/src/registrar/templates/domain_request_requesting_entity.html
index 375f7ce74..ed8dd771c 100644
--- a/src/registrar/templates/domain_request_requesting_entity.html
+++ b/src/registrar/templates/domain_request_requesting_entity.html
@@ -13,4 +13,4 @@
{% input_with_errors forms.0.organization_name %}
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py
index 8530859e2..6ff9ae218 100644
--- a/src/registrar/tests/test_views_request.py
+++ b/src/registrar/tests/test_views_request.py
@@ -2742,6 +2742,64 @@ class DomainRequestTests(TestWithUser, WebTest):
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}))
+ self.assertEqual(edit_page.status_code, 200)
+
+ # Cleanup
+ portfolio_perm.delete()
+ portfolio.delete()
+
+ def test_non_creator_access(self):
+ """Tests that a user cannot edit a domain request they didn't create"""
+ other_user = User.objects.create_user(username="other_user", password="password")
+ 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}))
+ self.assertEqual(edit_page.status_code, 200)
+
class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
def setUp(self):
@@ -2904,7 +2962,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
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)
@@ -2928,7 +2986,10 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
def test_unlocked_steps_full_domain_request(self):
"""Test when all fields in the domain request are filled."""
- domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ user=self.user
+ )
domain_request.anything_else = False
domain_request.has_anything_else_text = False
domain_request.save()
@@ -3026,6 +3087,93 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
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,
+ )
+
+ 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)
+ print(f"what is the page? {detail_page}")
+
+ # 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: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):
diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py
index 263e57be1..1a5cf48cb 100644
--- a/src/registrar/views/domain_request.py
+++ b/src/registrar/views/domain_request.py
@@ -136,8 +136,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
portfolio=portfolio,
)
- # TODO - is this needed?
- if portfolio:
+ # Question for reviewers: we should probably be doing this right?
+ if portfolio and not self._domain_request.generic_org_type:
self._domain_request.generic_org_type = portfolio.organization_type
self._domain_request.save()
else:
@@ -505,17 +505,14 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# which button did the user press?
button: str = request.POST.get("submit_button", "")
- # If a user hits the new request url directly
+
if "new_request" not in request.session:
request.session["new_request"] = True
+
# if user has acknowledged the intro message
if button == "intro_acknowledge":
- if request.path_info == self.NEW_URL_NAME:
-
- if self.request.session["new_request"] is True:
- # This will trigger the domain_request getter into creating a new DomainRequest
- del self.storage
- return self.goto(self.steps.first)
+ # Split into a function: C901 'DomainRequestWizard.post' is too complex (11)
+ self.handle_intro_acknowledge(request)
# if accessing this class directly, redirect to the first step
if self.__class__ == DomainRequestWizard:
@@ -546,6 +543,14 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# otherwise, proceed as normal
return self.goto_next_step()
+ def handle_intro_acknowledge(self, request):
+ """If we are starting a new request, clear storage
+ and redirect to the first step"""
+ if request.path_info == self.NEW_URL_NAME:
+ if self.request.session["new_request"] is True:
+ del self.storage
+ return self.goto(self.steps.first)
+
def save(self, forms: list):
"""
Unpack the form responses onto the model object properties.
@@ -576,11 +581,13 @@ class PortfolioDomainRequestWizard(DomainRequestWizard):
self.steps = StepsHelper(self)
self._domain_request = None # for caching
+
# Portfolio pages
class RequestingEntity(DomainRequestWizard):
template_name = "domain_request_requesting_entity.html"
forms = [forms.RequestingEntityForm]
+
# Non-portfolio pages
class OrganizationType(DomainRequestWizard):
template_name = "domain_request_org_type.html"
From e9413a53bda8bcdcb9824e382b6cb9b9e1d6e0cd Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 4 Oct 2024 09:58:27 -0600
Subject: [PATCH 06/22] Lint + fix tests
---
src/registrar/tests/test_views_request.py | 16 +++++++---------
1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py
index 6ff9ae218..180cb3f98 100644
--- a/src/registrar/tests/test_views_request.py
+++ b/src/registrar/tests/test_views_request.py
@@ -2771,17 +2771,18 @@ class DomainRequestTests(TestWithUser, WebTest):
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}))
+ 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()
@@ -2797,7 +2798,7 @@ class DomainRequestTests(TestWithUser, WebTest):
"""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}))
+ edit_page = self.app.get(reverse("edit-domain-request", kwargs={"id": domain_request.pk})).follow()
self.assertEqual(edit_page.status_code, 200)
@@ -2986,10 +2987,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
def test_unlocked_steps_full_domain_request(self):
"""Test when all fields in the domain request are filled."""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- user=self.user
- )
+ domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
domain_request.anything_else = False
domain_request.has_anything_else_text = False
domain_request.save()
@@ -3099,7 +3097,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
- 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(
@@ -3167,7 +3165,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
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()
From 133155fb0bd63b82f14c2bc23e77270701634b09 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 4 Oct 2024 11:45:02 -0600
Subject: [PATCH 07/22] Custom additional details page
---
src/registrar/config/urls.py | 1 +
.../portfolio_request_review_steps.html | 28 +--------
...lio_domain_request_additional_details.html | 22 +++++++
src/registrar/utility/enums.py | 9 +--
src/registrar/views/domain_request.py | 60 +++++++++----------
5 files changed, 58 insertions(+), 62 deletions(-)
create mode 100644 src/registrar/templates/portfolio_domain_request_additional_details.html
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index e7f1883cd..57b84e2d5 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -62,6 +62,7 @@ for step, view in [
(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))
diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html
index 1c4286baa..9d3c5bdeb 100644
--- a/src/registrar/templates/includes/portfolio_request_review_steps.html
+++ b/src/registrar/templates/includes/portfolio_request_review_steps.html
@@ -53,32 +53,8 @@
{% 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 %}
-
CISA Regional Representative
-
- {% if domain_request.cisa_representative_first_name %}
-