From f1ab40a3a075eea2c59d1f2ece3816e3e0d3c8a8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:16:35 -0600 Subject: [PATCH 01/22] Modify domain request wizard to show portfolio views --- src/registrar/config/urls.py | 11 +- src/registrar/forms/domain_request_wizard.py | 9 ++ .../domain_request_requesting_entity.html | 16 +++ .../templates/domain_request_review.html | 6 +- .../portfolio_request_review_steps.html | 1 - src/registrar/utility/enums.py | 3 +- src/registrar/views/domain_request.py | 136 +++++++++++++----- 7 files changed, 139 insertions(+), 43 deletions(-) create mode 100644 src/registrar/templates/domain_request_requesting_entity.html diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index df5733238..ba93d1dec 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -22,7 +22,7 @@ from registrar.views.report_views import ( ExportDataTypeUser, ) -from registrar.views.domain_request import Step +from registrar.views.domain_request import Step, PortfolioDomainRequestStep from registrar.views.domain_requests_json import get_domain_requests_json from registrar.views.transfer_user import TransferUserView from registrar.views.utility.api_views import ( @@ -58,6 +58,15 @@ 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/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index f2fdd32bc..c40184a20 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -21,6 +21,15 @@ from registrar.utility.constants import BranchChoices 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.") + }, + ) + + class OrganizationTypeForm(RegistrarForm): generic_org_type = forms.ChoiceField( # use the long names in the domain request form diff --git a/src/registrar/templates/domain_request_requesting_entity.html b/src/registrar/templates/domain_request_requesting_entity.html new file mode 100644 index 000000000..831c37984 --- /dev/null +++ b/src/registrar/templates/domain_request_requesting_entity.html @@ -0,0 +1,16 @@ +{% extends 'domain_request_form.html' %} +{% load field_helpers url_helpers %} + +{% block form_instructions %} +

🛸🛸🛸🛸 Placeholder content 🛸🛸🛸🛸

+{% endblock %} + +{% block form_fields %} +
+ +

Are you from the polaris star system?

+
+ + {% input_with_errors forms.0.is_policy_acknowledged %} +
+{% 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.

diff --git a/src/registrar/templates/domain_request_intro.html b/src/registrar/templates/domain_request_intro.html index fd94e0ef1..6b5223991 100644 --- a/src/registrar/templates/domain_request_intro.html +++ b/src/registrar/templates/domain_request_intro.html @@ -12,7 +12,11 @@

You’re about to start your .gov domain request.

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.

+ {% endif %}

Time to complete the form

If you have all the information you need, completing your domain request might take around 15 minutes.

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

Are you from the polaris star system?

+

What is the name of your space vessel?

- {% input_with_errors forms.0.is_policy_acknowledged %} + {% input_with_errors forms.0.organization_name %}
{% 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 @@