mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-26 04:28:39 +02:00
Merge branch 'main' into bob/2416-portfolio-admin-emails
This commit is contained in:
commit
cf516778a3
28 changed files with 334 additions and 370 deletions
61
.github/ISSUE_TEMPLATE/story.yml
vendored
61
.github/ISSUE_TEMPLATE/story.yml
vendored
|
@ -1,61 +0,0 @@
|
||||||
name: Story
|
|
||||||
description: Capture actionable sprint work
|
|
||||||
labels: ["story"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
id: help
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
> **Note**
|
|
||||||
> GitHub Issues use [GitHub Flavored Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) for formatting.
|
|
||||||
- type: textarea
|
|
||||||
id: story
|
|
||||||
attributes:
|
|
||||||
label: Story
|
|
||||||
description: |
|
|
||||||
Please add the "as a, I want, so that" details that describe the story.
|
|
||||||
If more than one "as a, I want, so that" describes the story, add multiple.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
As an analyst
|
|
||||||
I want the ability to approve a domain request
|
|
||||||
so that a request can be fulfilled and a new .gov domain can be provisioned
|
|
||||||
value: |
|
|
||||||
As a
|
|
||||||
I want
|
|
||||||
so that
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: acceptance-criteria
|
|
||||||
attributes:
|
|
||||||
label: Acceptance Criteria
|
|
||||||
description: |
|
|
||||||
Please add the acceptance criteria that best describe the desired outcomes when this work is completed
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- Application sends an email when analysts approve domain requests
|
|
||||||
- Domain request status is "approved"
|
|
||||||
|
|
||||||
Example ("given, when, then" format):
|
|
||||||
Given that I am an analyst who has finished reviewing a domain request
|
|
||||||
When I click to approve a domain request
|
|
||||||
Then the domain provisioning process should be initiated, and the applicant should receive an email update.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: additional-context
|
|
||||||
attributes:
|
|
||||||
label: Additional Context
|
|
||||||
description: "Please include additional references (screenshots, design links, documentation, etc.) that are relevant"
|
|
||||||
- type: textarea
|
|
||||||
id: issue-links
|
|
||||||
attributes:
|
|
||||||
label: Issue Links
|
|
||||||
description: |
|
|
||||||
What other issues does this story relate to and how?
|
|
||||||
|
|
||||||
Example:
|
|
||||||
- 🚧 Blocked by: #123
|
|
||||||
- 🔄 Relates to: #234
|
|
|
@ -20,7 +20,6 @@ from registrar.views.report_views import (
|
||||||
AnalyticsView,
|
AnalyticsView,
|
||||||
ExportDomainRequestDataFull,
|
ExportDomainRequestDataFull,
|
||||||
ExportDataTypeUser,
|
ExportDataTypeUser,
|
||||||
ExportDataTypeRequests,
|
|
||||||
ExportMembersPortfolio,
|
ExportMembersPortfolio,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -260,11 +259,6 @@ urlpatterns = [
|
||||||
ExportDataTypeUser.as_view(),
|
ExportDataTypeUser.as_view(),
|
||||||
name="export_data_type_user",
|
name="export_data_type_user",
|
||||||
),
|
),
|
||||||
path(
|
|
||||||
"reports/export_data_type_requests/",
|
|
||||||
ExportDataTypeRequests.as_view(),
|
|
||||||
name="export_data_type_requests",
|
|
||||||
),
|
|
||||||
path(
|
path(
|
||||||
"domain-request/<int:id>/edit/",
|
"domain-request/<int:id>/edit/",
|
||||||
views.DomainRequestWizard.as_view(),
|
views.DomainRequestWizard.as_view(),
|
||||||
|
|
|
@ -56,12 +56,11 @@ def add_path_to_context(request):
|
||||||
def portfolio_permissions(request):
|
def portfolio_permissions(request):
|
||||||
"""Make portfolio permissions for the request user available in global context"""
|
"""Make portfolio permissions for the request user available in global context"""
|
||||||
portfolio_context = {
|
portfolio_context = {
|
||||||
"has_base_portfolio_permission": False,
|
"has_view_portfolio_permission": False,
|
||||||
|
"has_edit_portfolio_permission": False,
|
||||||
"has_any_domains_portfolio_permission": False,
|
"has_any_domains_portfolio_permission": False,
|
||||||
"has_any_requests_portfolio_permission": False,
|
"has_any_requests_portfolio_permission": False,
|
||||||
"has_edit_request_portfolio_permission": False,
|
"has_edit_request_portfolio_permission": False,
|
||||||
"has_view_suborganization_portfolio_permission": False,
|
|
||||||
"has_edit_suborganization_portfolio_permission": False,
|
|
||||||
"has_view_members_portfolio_permission": False,
|
"has_view_members_portfolio_permission": False,
|
||||||
"has_edit_members_portfolio_permission": False,
|
"has_edit_members_portfolio_permission": False,
|
||||||
"portfolio": None,
|
"portfolio": None,
|
||||||
|
@ -82,15 +81,11 @@ def portfolio_permissions(request):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Linting: line too long
|
|
||||||
view_suborg = request.user.has_view_suborganization_portfolio_permission(portfolio)
|
|
||||||
edit_suborg = request.user.has_edit_suborganization_portfolio_permission(portfolio)
|
|
||||||
if portfolio:
|
if portfolio:
|
||||||
return {
|
return {
|
||||||
"has_base_portfolio_permission": request.user.has_base_portfolio_permission(portfolio),
|
"has_view_portfolio_permission": request.user.has_view_portfolio_permission(portfolio),
|
||||||
|
"has_edit_portfolio_permission": request.user.has_edit_portfolio_permission(portfolio),
|
||||||
"has_edit_request_portfolio_permission": request.user.has_edit_request_portfolio_permission(portfolio),
|
"has_edit_request_portfolio_permission": request.user.has_edit_request_portfolio_permission(portfolio),
|
||||||
"has_view_suborganization_portfolio_permission": view_suborg,
|
|
||||||
"has_edit_suborganization_portfolio_permission": edit_suborg,
|
|
||||||
"has_any_domains_portfolio_permission": request.user.has_any_domains_portfolio_permission(portfolio),
|
"has_any_domains_portfolio_permission": request.user.has_any_domains_portfolio_permission(portfolio),
|
||||||
"has_any_requests_portfolio_permission": request.user.has_any_requests_portfolio_permission(portfolio),
|
"has_any_requests_portfolio_permission": request.user.has_any_requests_portfolio_permission(portfolio),
|
||||||
"has_view_members_portfolio_permission": request.user.has_view_members_portfolio_permission(portfolio),
|
"has_view_members_portfolio_permission": request.user.has_view_members_portfolio_permission(portfolio),
|
||||||
|
|
|
@ -2,7 +2,8 @@ from __future__ import annotations # allows forward references in annotations
|
||||||
import logging
|
import logging
|
||||||
from api.views import DOMAIN_API_MESSAGES
|
from api.views import DOMAIN_API_MESSAGES
|
||||||
from phonenumber_field.formfields import PhoneNumberField # type: ignore
|
from phonenumber_field.formfields import PhoneNumberField # type: ignore
|
||||||
|
from registrar.models.portfolio import Portfolio
|
||||||
|
from registrar.utility.waffle import flag_is_active_anywhere
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.validators import RegexValidator, MaxLengthValidator
|
from django.core.validators import RegexValidator, MaxLengthValidator
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -321,7 +322,8 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
# if it has been filled in when required.
|
# if it has been filled in when required.
|
||||||
# uncomment to see if modelChoiceField can be an arg later
|
# uncomment to see if modelChoiceField can be an arg later
|
||||||
required=False,
|
required=False,
|
||||||
queryset=FederalAgency.objects.exclude(agency__in=excluded_agencies),
|
# We populate this queryset in init.
|
||||||
|
queryset=FederalAgency.objects.none(),
|
||||||
widget=ComboboxWidget,
|
widget=ComboboxWidget,
|
||||||
)
|
)
|
||||||
organization_name = forms.CharField(
|
organization_name = forms.CharField(
|
||||||
|
@ -363,6 +365,20 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
label="Urbanization (required for Puerto Rico only)",
|
label="Urbanization (required for Puerto Rico only)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Set the queryset for federal agency.
|
||||||
|
# If the organization_requests flag is active, We want to exclude agencies with a portfolio.
|
||||||
|
federal_agency_queryset = FederalAgency.objects.exclude(agency__in=self.excluded_agencies)
|
||||||
|
if flag_is_active_anywhere("organization_feature") and flag_is_active_anywhere("organization_requests"):
|
||||||
|
# Exclude both predefined agencies and those matching portfolio records in one query
|
||||||
|
federal_agency_queryset = federal_agency_queryset.exclude(
|
||||||
|
id__in=Portfolio.objects.values_list("federal_agency__id", flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.fields["federal_agency"].queryset = federal_agency_queryset
|
||||||
|
|
||||||
def clean_federal_agency(self):
|
def clean_federal_agency(self):
|
||||||
"""Require something to be selected when this is a federal agency."""
|
"""Require something to be selected when this is a federal agency."""
|
||||||
federal_agency = self.cleaned_data.get("federal_agency", None)
|
federal_agency = self.cleaned_data.get("federal_agency", None)
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Generated by Django 4.2.10 on 2025-02-04 11:18
|
||||||
|
|
||||||
|
import django.contrib.postgres.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0139_alter_domainrequest_action_needed_reason"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="portfolioinvitation",
|
||||||
|
name="additional_permissions",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("view_all_domains", "View all domains and domain reports"),
|
||||||
|
("view_managed_domains", "View managed domains"),
|
||||||
|
("view_members", "View members"),
|
||||||
|
("edit_members", "Create and edit members"),
|
||||||
|
("view_all_requests", "View all requests"),
|
||||||
|
("edit_requests", "Create and edit requests"),
|
||||||
|
("view_portfolio", "View organization"),
|
||||||
|
("edit_portfolio", "Edit organization"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
help_text="Select one or more additional permissions.",
|
||||||
|
null=True,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="userportfoliopermission",
|
||||||
|
name="additional_permissions",
|
||||||
|
field=django.contrib.postgres.fields.ArrayField(
|
||||||
|
base_field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("view_all_domains", "View all domains and domain reports"),
|
||||||
|
("view_managed_domains", "View managed domains"),
|
||||||
|
("view_members", "View members"),
|
||||||
|
("edit_members", "Create and edit members"),
|
||||||
|
("view_all_requests", "View all requests"),
|
||||||
|
("edit_requests", "Create and edit requests"),
|
||||||
|
("view_portfolio", "View organization"),
|
||||||
|
("edit_portfolio", "Edit organization"),
|
||||||
|
],
|
||||||
|
max_length=50,
|
||||||
|
),
|
||||||
|
blank=True,
|
||||||
|
help_text="Select one or more additional permissions.",
|
||||||
|
null=True,
|
||||||
|
size=None,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -15,8 +15,7 @@ from registrar.utility.constants import BranchChoices
|
||||||
from auditlog.models import LogEntry
|
from auditlog.models import LogEntry
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
|
||||||
from registrar.utility.waffle import flag_is_active_for_user
|
from registrar.utility.waffle import flag_is_active_for_user, flag_is_active_anywhere
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
from ..utility.email import send_templated_email, EmailSendingError
|
from ..utility.email import send_templated_email, EmailSendingError
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
@ -947,7 +946,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
try:
|
try:
|
||||||
if not context:
|
if not context:
|
||||||
has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature")
|
has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature")
|
||||||
is_org_user = has_organization_feature_flag and recipient.has_base_portfolio_permission(self.portfolio)
|
is_org_user = has_organization_feature_flag and recipient.has_view_portfolio_permission(self.portfolio)
|
||||||
context = {
|
context = {
|
||||||
"domain_request": self,
|
"domain_request": self,
|
||||||
# This is the user that we refer to in the email
|
# This is the user that we refer to in the email
|
||||||
|
@ -1299,6 +1298,40 @@ class DomainRequest(TimeStampedModel):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def unlock_organization_contact(self) -> bool:
|
||||||
|
"""Unlocks the organization_contact step."""
|
||||||
|
if flag_is_active_anywhere("organization_feature") and flag_is_active_anywhere("organization_requests"):
|
||||||
|
# Check if the current federal agency is an outlawed one
|
||||||
|
if self.organization_type == self.OrganizationChoices.FEDERAL and self.federal_agency:
|
||||||
|
Portfolio = apps.get_model("registrar.Portfolio")
|
||||||
|
return (
|
||||||
|
FederalAgency.objects.exclude(
|
||||||
|
id__in=Portfolio.objects.values_list("federal_agency__id", flat=True),
|
||||||
|
)
|
||||||
|
.filter(id=self.federal_agency.id)
|
||||||
|
.exists()
|
||||||
|
)
|
||||||
|
return bool(
|
||||||
|
self.federal_agency is not None
|
||||||
|
or self.organization_name is not None
|
||||||
|
or self.address_line1 is not None
|
||||||
|
or self.city is not None
|
||||||
|
or self.state_territory is not None
|
||||||
|
or self.zipcode is not None
|
||||||
|
or self.urbanization is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
def unlock_other_contacts(self) -> bool:
|
||||||
|
"""Unlocks the other contacts step"""
|
||||||
|
other_contacts_filled_out = self.other_contacts.filter(
|
||||||
|
first_name__isnull=False,
|
||||||
|
last_name__isnull=False,
|
||||||
|
title__isnull=False,
|
||||||
|
email__isnull=False,
|
||||||
|
phone__isnull=False,
|
||||||
|
).exists()
|
||||||
|
return (self.has_other_contacts() and other_contacts_filled_out) or self.no_other_contacts_rationale is not None
|
||||||
|
|
||||||
# ## Form policies ## #
|
# ## Form policies ## #
|
||||||
#
|
#
|
||||||
# These methods control what questions need to be answered by applicants
|
# These methods control what questions need to be answered by applicants
|
||||||
|
@ -1396,140 +1429,6 @@ class DomainRequest(TimeStampedModel):
|
||||||
names = [n for n in [self.cisa_representative_first_name, self.cisa_representative_last_name] if n]
|
names = [n for n in [self.cisa_representative_first_name, self.cisa_representative_last_name] if n]
|
||||||
return " ".join(names) if names else "Unknown"
|
return " ".join(names) if names else "Unknown"
|
||||||
|
|
||||||
def _is_federal_complete(self):
|
|
||||||
# Federal -> "Federal government branch" page can't be empty + Federal Agency selection can't be None
|
|
||||||
return not (self.federal_type is None or self.federal_agency is None)
|
|
||||||
|
|
||||||
def _is_interstate_complete(self):
|
|
||||||
# Interstate -> "About your organization" page can't be empty
|
|
||||||
return self.about_your_organization is not None
|
|
||||||
|
|
||||||
def _is_state_or_territory_complete(self):
|
|
||||||
# State -> ""Election office" page can't be empty
|
|
||||||
return self.is_election_board is not None
|
|
||||||
|
|
||||||
def _is_tribal_complete(self):
|
|
||||||
# Tribal -> "Tribal name" and "Election office" page can't be empty
|
|
||||||
return self.tribe_name is not None and self.is_election_board is not None
|
|
||||||
|
|
||||||
def _is_county_complete(self):
|
|
||||||
# County -> "Election office" page can't be empty
|
|
||||||
return self.is_election_board is not None
|
|
||||||
|
|
||||||
def _is_city_complete(self):
|
|
||||||
# City -> "Election office" page can't be empty
|
|
||||||
return self.is_election_board is not None
|
|
||||||
|
|
||||||
def _is_special_district_complete(self):
|
|
||||||
# Special District -> "Election office" and "About your organization" page can't be empty
|
|
||||||
return self.is_election_board is not None and self.about_your_organization is not None
|
|
||||||
|
|
||||||
# Do we still want to test this after creator is autogenerated? Currently it went back to being selectable
|
|
||||||
def _is_creator_complete(self):
|
|
||||||
return self.creator is not None
|
|
||||||
|
|
||||||
def _is_organization_name_and_address_complete(self):
|
|
||||||
return not (
|
|
||||||
self.organization_name is None
|
|
||||||
and self.address_line1 is None
|
|
||||||
and self.city is None
|
|
||||||
and self.state_territory is None
|
|
||||||
and self.zipcode is None
|
|
||||||
)
|
|
||||||
|
|
||||||
def _is_senior_official_complete(self):
|
|
||||||
return self.senior_official is not None
|
|
||||||
|
|
||||||
def _is_requested_domain_complete(self):
|
|
||||||
return self.requested_domain is not None
|
|
||||||
|
|
||||||
def _is_purpose_complete(self):
|
|
||||||
return self.purpose is not None
|
|
||||||
|
|
||||||
def _has_other_contacts_and_filled(self):
|
|
||||||
# Other Contacts Radio button is Yes and if all required fields are filled
|
|
||||||
return (
|
|
||||||
self.has_other_contacts()
|
|
||||||
and self.other_contacts.filter(
|
|
||||||
first_name__isnull=False,
|
|
||||||
last_name__isnull=False,
|
|
||||||
title__isnull=False,
|
|
||||||
email__isnull=False,
|
|
||||||
phone__isnull=False,
|
|
||||||
).exists()
|
|
||||||
)
|
|
||||||
|
|
||||||
def _has_no_other_contacts_gives_rationale(self):
|
|
||||||
# Other Contacts Radio button is No and a rationale is provided
|
|
||||||
return self.has_other_contacts() is False and self.no_other_contacts_rationale is not None
|
|
||||||
|
|
||||||
def _is_other_contacts_complete(self):
|
|
||||||
if self._has_other_contacts_and_filled() or self._has_no_other_contacts_gives_rationale():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _cisa_rep_check(self):
|
|
||||||
# Either does not have a CISA rep, OR has a CISA rep + both first name
|
|
||||||
# and last name are NOT empty and are NOT an empty string
|
|
||||||
to_return = (
|
|
||||||
self.has_cisa_representative is True
|
|
||||||
and self.cisa_representative_first_name is not None
|
|
||||||
and self.cisa_representative_first_name != ""
|
|
||||||
and self.cisa_representative_last_name is not None
|
|
||||||
and self.cisa_representative_last_name != ""
|
|
||||||
) or self.has_cisa_representative is False
|
|
||||||
|
|
||||||
return to_return
|
|
||||||
|
|
||||||
def _anything_else_radio_button_and_text_field_check(self):
|
|
||||||
# Anything else boolean is True + filled text field and it's not an empty string OR the boolean is No
|
|
||||||
return (
|
|
||||||
self.has_anything_else_text is True and self.anything_else is not None and self.anything_else != ""
|
|
||||||
) or self.has_anything_else_text is False
|
|
||||||
|
|
||||||
def _is_additional_details_complete(self):
|
|
||||||
return self._cisa_rep_check() and self._anything_else_radio_button_and_text_field_check()
|
|
||||||
|
|
||||||
def _is_policy_acknowledgement_complete(self):
|
|
||||||
return self.is_policy_acknowledged is not None
|
|
||||||
|
|
||||||
def _is_general_form_complete(self, request):
|
|
||||||
return (
|
|
||||||
self._is_creator_complete()
|
|
||||||
and self._is_organization_name_and_address_complete()
|
|
||||||
and self._is_senior_official_complete()
|
|
||||||
and self._is_requested_domain_complete()
|
|
||||||
and self._is_purpose_complete()
|
|
||||||
and self._is_other_contacts_complete()
|
|
||||||
and self._is_additional_details_complete()
|
|
||||||
and self._is_policy_acknowledgement_complete()
|
|
||||||
)
|
|
||||||
|
|
||||||
def _form_complete(self, request):
|
|
||||||
match self.generic_org_type:
|
|
||||||
case DomainRequest.OrganizationChoices.FEDERAL:
|
|
||||||
is_complete = self._is_federal_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.INTERSTATE:
|
|
||||||
is_complete = self._is_interstate_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.STATE_OR_TERRITORY:
|
|
||||||
is_complete = self._is_state_or_territory_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.TRIBAL:
|
|
||||||
is_complete = self._is_tribal_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.COUNTY:
|
|
||||||
is_complete = self._is_county_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.CITY:
|
|
||||||
is_complete = self._is_city_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.SPECIAL_DISTRICT:
|
|
||||||
is_complete = self._is_special_district_complete()
|
|
||||||
case DomainRequest.OrganizationChoices.SCHOOL_DISTRICT:
|
|
||||||
is_complete = True
|
|
||||||
case _:
|
|
||||||
# NOTE: Shouldn't happen, this is only if somehow they didn't choose an org type
|
|
||||||
is_complete = False
|
|
||||||
if not is_complete or not self._is_general_form_complete(request):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
"""The following converted_ property methods get field data from this domain request's portfolio,
|
"""The following converted_ property methods get field data from this domain request's portfolio,
|
||||||
if there is an associated portfolio. If not, they return data from the domain request model."""
|
if there is an associated portfolio. If not, they return data from the domain request model."""
|
||||||
|
|
||||||
|
|
|
@ -210,10 +210,10 @@ class User(AbstractUser):
|
||||||
|
|
||||||
return portfolio_permission in user_portfolio_perms._get_portfolio_permissions()
|
return portfolio_permission in user_portfolio_perms._get_portfolio_permissions()
|
||||||
|
|
||||||
def has_base_portfolio_permission(self, portfolio):
|
def has_view_portfolio_permission(self, portfolio):
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_PORTFOLIO)
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_PORTFOLIO)
|
||||||
|
|
||||||
def has_edit_org_portfolio_permission(self, portfolio):
|
def has_edit_portfolio_permission(self, portfolio):
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_PORTFOLIO)
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_PORTFOLIO)
|
||||||
|
|
||||||
def has_any_domains_portfolio_permission(self, portfolio):
|
def has_any_domains_portfolio_permission(self, portfolio):
|
||||||
|
@ -268,13 +268,6 @@ class User(AbstractUser):
|
||||||
def has_edit_request_portfolio_permission(self, portfolio):
|
def has_edit_request_portfolio_permission(self, portfolio):
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_REQUESTS)
|
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_REQUESTS)
|
||||||
|
|
||||||
# Field specific permission checks
|
|
||||||
def has_view_suborganization_portfolio_permission(self, portfolio):
|
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION)
|
|
||||||
|
|
||||||
def has_edit_suborganization_portfolio_permission(self, portfolio):
|
|
||||||
return self._has_portfolio_permission(portfolio, UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION)
|
|
||||||
|
|
||||||
def is_portfolio_admin(self, portfolio):
|
def is_portfolio_admin(self, portfolio):
|
||||||
return "Admin" in self.portfolio_role_summary(portfolio)
|
return "Admin" in self.portfolio_role_summary(portfolio)
|
||||||
|
|
||||||
|
@ -293,7 +286,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
# Define the conditions and their corresponding roles
|
# Define the conditions and their corresponding roles
|
||||||
conditions_roles = [
|
conditions_roles = [
|
||||||
(self.has_edit_suborganization_portfolio_permission(portfolio), ["Admin"]),
|
(self.has_edit_portfolio_permission(portfolio), ["Admin"]),
|
||||||
(
|
(
|
||||||
self.has_view_all_domains_portfolio_permission(portfolio)
|
self.has_view_all_domains_portfolio_permission(portfolio)
|
||||||
and self.has_any_requests_portfolio_permission(portfolio)
|
and self.has_any_requests_portfolio_permission(portfolio)
|
||||||
|
@ -306,20 +299,20 @@ class User(AbstractUser):
|
||||||
["View-only admin"],
|
["View-only admin"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
self.has_base_portfolio_permission(portfolio)
|
self.has_view_portfolio_permission(portfolio)
|
||||||
and self.has_edit_request_portfolio_permission(portfolio)
|
and self.has_edit_request_portfolio_permission(portfolio)
|
||||||
and self.has_any_domains_portfolio_permission(portfolio),
|
and self.has_any_domains_portfolio_permission(portfolio),
|
||||||
["Domain requestor", "Domain manager"],
|
["Domain requestor", "Domain manager"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
self.has_base_portfolio_permission(portfolio) and self.has_edit_request_portfolio_permission(portfolio),
|
self.has_view_portfolio_permission(portfolio) and self.has_edit_request_portfolio_permission(portfolio),
|
||||||
["Domain requestor"],
|
["Domain requestor"],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
self.has_base_portfolio_permission(portfolio) and self.has_any_domains_portfolio_permission(portfolio),
|
self.has_view_portfolio_permission(portfolio) and self.has_any_domains_portfolio_permission(portfolio),
|
||||||
["Domain manager"],
|
["Domain manager"],
|
||||||
),
|
),
|
||||||
(self.has_base_portfolio_permission(portfolio), ["Member"]),
|
(self.has_view_portfolio_permission(portfolio), ["Member"]),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Evaluate conditions and add roles
|
# Evaluate conditions and add roles
|
||||||
|
@ -477,7 +470,7 @@ class User(AbstractUser):
|
||||||
def is_org_user(self, request):
|
def is_org_user(self, request):
|
||||||
has_organization_feature_flag = flag_is_active(request, "organization_feature")
|
has_organization_feature_flag = flag_is_active(request, "organization_feature")
|
||||||
portfolio = request.session.get("portfolio")
|
portfolio = request.session.get("portfolio")
|
||||||
return has_organization_feature_flag and self.has_base_portfolio_permission(portfolio)
|
return has_organization_feature_flag and self.has_view_portfolio_permission(portfolio)
|
||||||
|
|
||||||
def get_user_domain_ids(self, request):
|
def get_user_domain_ids(self, request):
|
||||||
"""Returns either the domains ids associated with this user on UserDomainRole or Portfolio"""
|
"""Returns either the domains ids associated with this user on UserDomainRole or Portfolio"""
|
||||||
|
|
|
@ -27,13 +27,10 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION,
|
|
||||||
UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION,
|
|
||||||
],
|
],
|
||||||
# NOTE: Check FORBIDDEN_PORTFOLIO_ROLE_PERMISSIONS before adding roles here.
|
# NOTE: Check FORBIDDEN_PORTFOLIO_ROLE_PERMISSIONS before adding roles here.
|
||||||
UserPortfolioRoleChoices.ORGANIZATION_MEMBER: [
|
UserPortfolioRoleChoices.ORGANIZATION_MEMBER: [
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION,
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +40,6 @@ class UserPortfolioPermission(TimeStampedModel):
|
||||||
UserPortfolioRoleChoices.ORGANIZATION_MEMBER: [
|
UserPortfolioRoleChoices.ORGANIZATION_MEMBER: [
|
||||||
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
UserPortfolioPermissionChoices.EDIT_MEMBERS,
|
||||||
UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION,
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,10 +42,6 @@ class UserPortfolioPermissionChoices(models.TextChoices):
|
||||||
VIEW_PORTFOLIO = "view_portfolio", "View organization"
|
VIEW_PORTFOLIO = "view_portfolio", "View organization"
|
||||||
EDIT_PORTFOLIO = "edit_portfolio", "Edit organization"
|
EDIT_PORTFOLIO = "edit_portfolio", "Edit organization"
|
||||||
|
|
||||||
# Domain: field specific permissions
|
|
||||||
VIEW_SUBORGANIZATION = "view_suborganization", "View suborganization"
|
|
||||||
EDIT_SUBORGANIZATION = "edit_suborganization", "Edit suborganization"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_user_portfolio_permission_label(cls, user_portfolio_permission):
|
def get_user_portfolio_permission_label(cls, user_portfolio_permission):
|
||||||
return cls(user_portfolio_permission).label if user_portfolio_permission else None
|
return cls(user_portfolio_permission).label if user_portfolio_permission else None
|
||||||
|
|
|
@ -103,12 +103,12 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if portfolio %}
|
{% if portfolio %}
|
||||||
{% if has_any_domains_portfolio_permission and has_edit_suborganization_portfolio_permission %}
|
{% if has_any_domains_portfolio_permission and has_edit_portfolio_permission %}
|
||||||
{% url 'domain-suborganization' pk=domain.id as url %}
|
{% url 'domain-suborganization' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_edit_suborganization_portfolio_permission %}
|
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_edit_portfolio_permission %}
|
||||||
{% elif has_any_domains_portfolio_permission and has_view_suborganization_portfolio_permission %}
|
{% elif has_any_domains_portfolio_permission and has_view_portfolio_permission %}
|
||||||
{% url 'domain-suborganization' pk=domain.id as url %}
|
{% url 'domain-suborganization' pk=domain.id as url %}
|
||||||
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_view_suborganization_portfolio_permission view_button=True %}
|
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_view_portfolio_permission view_button=True %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% url 'domain-org-name-address' pk=domain.id as url %}
|
{% url 'domain-org-name-address' pk=domain.id as url %}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
{% if portfolio %}
|
{% if portfolio %}
|
||||||
{% comment %} Only show this menu option if the user has the perms to do so {% endcomment %}
|
{% comment %} Only show this menu option if the user has the perms to do so {% endcomment %}
|
||||||
{% if has_any_domains_portfolio_permission and has_view_suborganization_portfolio_permission %}
|
{% if has_any_domains_portfolio_permission and has_view_portfolio_permission %}
|
||||||
{% with url_name="domain-suborganization" %}
|
{% with url_name="domain-suborganization" %}
|
||||||
{% include "includes/domain_sidenav_item.html" with item_text="Suborganization" %}
|
{% include "includes/domain_sidenav_item.html" with item_text="Suborganization" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
please contact <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
please contact <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if has_any_domains_portfolio_permission and has_edit_suborganization_portfolio_permission %}
|
{% if has_any_domains_portfolio_permission and has_edit_portfolio_permission %}
|
||||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% input_with_errors form.sub_organization %}
|
{% input_with_errors form.sub_organization %}
|
||||||
|
|
|
@ -51,20 +51,7 @@
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{% if portfolio %}
|
|
||||||
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}" id="export-csv">
|
|
||||||
<section aria-label="Domain Requests report component" class="margin-top-205">
|
|
||||||
<!----------------------------------------------------------------------
|
|
||||||
This link is commented out because we intend to add it back in later.
|
|
||||||
------------------------------------------------------------------------->
|
|
||||||
<!-- <a href="{% url 'export_data_type_requests' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right">
|
|
||||||
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#file_download"></use>
|
|
||||||
</svg>Export as CSV
|
|
||||||
</a> -->
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if portfolio %}
|
{% if portfolio %}
|
||||||
|
|
|
@ -208,7 +208,7 @@
|
||||||
<th data-sortable="name" scope="col" role="columnheader">Domain name</th>
|
<th data-sortable="name" scope="col" role="columnheader">Domain name</th>
|
||||||
<th data-sortable="expiration_date" scope="col" role="columnheader">Expires</th>
|
<th data-sortable="expiration_date" scope="col" role="columnheader">Expires</th>
|
||||||
<th data-sortable="state_display" scope="col" role="columnheader">Status</th>
|
<th data-sortable="state_display" scope="col" role="columnheader">Status</th>
|
||||||
{% if portfolio and has_view_suborganization_portfolio_permission %}
|
{% if portfolio and has_view_portfolio_permission %}
|
||||||
<th data-sortable="domain_info__sub_organization" scope="col" role="columnheader">Suborganization</th>
|
<th data-sortable="domain_info__sub_organization" scope="col" role="columnheader">Suborganization</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th
|
<th
|
||||||
|
|
|
@ -92,11 +92,13 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if has_organization_members_flag %}
|
{% if has_organization_members_flag %}
|
||||||
|
{% if has_view_members_portfolio_permission %}
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
<a href="{% url 'members' %}" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
<a href="{% url 'members' %}" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
||||||
Members
|
Members
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="usa-nav__primary-item">
|
<li class="usa-nav__primary-item">
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.ORGANIZATION_CONTACT %}
|
{% if step == Step.ORGANIZATION_CONTACT %}
|
||||||
{% if domain_request.organization_name %}
|
{% if domain_request.unlock_organization_contact %}
|
||||||
{% with title=form_titles|get_item:step value=domain_request %}
|
{% 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' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url address='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -116,7 +116,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.OTHER_CONTACTS %}
|
{% if step == Step.OTHER_CONTACTS %}
|
||||||
{% if domain_request.other_contacts.all %}
|
{% if domain_request.unlock_other_contacts %}
|
||||||
{% with title=form_titles|get_item:step value=domain_request.other_contacts.all %}
|
{% with title=form_titles|get_item:step value=domain_request.other_contacts.all %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url contact='true' list='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url contact='true' list='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
|
|
||||||
<p>The name of your organization will be publicly listed as the domain registrant.</p>
|
<p>The name of your organization will be publicly listed as the domain registrant.</p>
|
||||||
|
|
||||||
{% if has_edit_org_portfolio_permission %}
|
{% if has_edit_portfolio_permission %}
|
||||||
<p>
|
<p>
|
||||||
Your organization name can’t be updated here.
|
Your organization name can’t be updated here.
|
||||||
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||||
|
|
|
@ -24,6 +24,7 @@ from registrar.forms.portfolio import (
|
||||||
PortfolioMemberForm,
|
PortfolioMemberForm,
|
||||||
PortfolioNewMemberForm,
|
PortfolioNewMemberForm,
|
||||||
)
|
)
|
||||||
|
from waffle.models import get_waffle_flag_model
|
||||||
from registrar.models.portfolio import Portfolio
|
from registrar.models.portfolio import Portfolio
|
||||||
from registrar.models.portfolio_invitation import PortfolioInvitation
|
from registrar.models.portfolio_invitation import PortfolioInvitation
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
|
@ -39,6 +40,10 @@ class TestFormValidation(MockEppLib):
|
||||||
self.API_BASE_PATH = "/api/v1/available/?domain="
|
self.API_BASE_PATH = "/api/v1/available/?domain="
|
||||||
self.user = get_user_model().objects.create(username="username")
|
self.user = get_user_model().objects.create(username="username")
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
# We use both of these flags in the test. In the normal app these are generated normally.
|
||||||
|
# The alternative syntax is adding the decorator to each test.
|
||||||
|
get_waffle_flag_model().objects.get_or_create(name="organization_feature")
|
||||||
|
get_waffle_flag_model().objects.get_or_create(name="organization_requests")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_org_contact_zip_invalid(self):
|
def test_org_contact_zip_invalid(self):
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from unittest.mock import Mock
|
||||||
from django.test import RequestFactory
|
from django.test import RequestFactory
|
||||||
|
from waffle.models import get_waffle_flag_model
|
||||||
|
from registrar.views.domain_request import DomainRequestWizard
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
DomainRequest,
|
DomainRequest,
|
||||||
|
@ -1190,8 +1191,8 @@ class TestUser(TestCase):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
@patch.object(User, "has_edit_suborganization_portfolio_permission", return_value=True)
|
@patch.object(User, "has_edit_portfolio_permission", return_value=True)
|
||||||
def test_portfolio_role_summary_admin(self, mock_edit_suborganization):
|
def test_portfolio_role_summary_admin(self, mock_edit_org):
|
||||||
# Test if the user is recognized as an Admin
|
# Test if the user is recognized as an Admin
|
||||||
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Admin"])
|
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Admin"])
|
||||||
|
|
||||||
|
@ -1216,7 +1217,7 @@ class TestUser(TestCase):
|
||||||
|
|
||||||
@patch.multiple(
|
@patch.multiple(
|
||||||
User,
|
User,
|
||||||
has_base_portfolio_permission=lambda self, portfolio: True,
|
has_view_portfolio_permission=lambda self, portfolio: True,
|
||||||
has_edit_request_portfolio_permission=lambda self, portfolio: True,
|
has_edit_request_portfolio_permission=lambda self, portfolio: True,
|
||||||
has_any_domains_portfolio_permission=lambda self, portfolio: True,
|
has_any_domains_portfolio_permission=lambda self, portfolio: True,
|
||||||
)
|
)
|
||||||
|
@ -1226,7 +1227,7 @@ class TestUser(TestCase):
|
||||||
|
|
||||||
@patch.multiple(
|
@patch.multiple(
|
||||||
User,
|
User,
|
||||||
has_base_portfolio_permission=lambda self, portfolio: True,
|
has_view_portfolio_permission=lambda self, portfolio: True,
|
||||||
has_edit_request_portfolio_permission=lambda self, portfolio: True,
|
has_edit_request_portfolio_permission=lambda self, portfolio: True,
|
||||||
)
|
)
|
||||||
def test_portfolio_role_summary_member_domain_requestor(self):
|
def test_portfolio_role_summary_member_domain_requestor(self):
|
||||||
|
@ -1235,14 +1236,14 @@ class TestUser(TestCase):
|
||||||
|
|
||||||
@patch.multiple(
|
@patch.multiple(
|
||||||
User,
|
User,
|
||||||
has_base_portfolio_permission=lambda self, portfolio: True,
|
has_view_portfolio_permission=lambda self, portfolio: True,
|
||||||
has_any_domains_portfolio_permission=lambda self, portfolio: True,
|
has_any_domains_portfolio_permission=lambda self, portfolio: True,
|
||||||
)
|
)
|
||||||
def test_portfolio_role_summary_member_domain_manager(self):
|
def test_portfolio_role_summary_member_domain_manager(self):
|
||||||
# Test if the user has 'Member' and 'Domain manager' roles
|
# Test if the user has 'Member' and 'Domain manager' roles
|
||||||
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Domain manager"])
|
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Domain manager"])
|
||||||
|
|
||||||
@patch.multiple(User, has_base_portfolio_permission=lambda self, portfolio: True)
|
@patch.multiple(User, has_view_portfolio_permission=lambda self, portfolio: True)
|
||||||
def test_portfolio_role_summary_member(self):
|
def test_portfolio_role_summary_member(self):
|
||||||
# Test if the user is recognized as a Member
|
# Test if the user is recognized as a Member
|
||||||
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Member"])
|
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), ["Member"])
|
||||||
|
@ -1252,17 +1253,17 @@ class TestUser(TestCase):
|
||||||
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), [])
|
self.assertEqual(self.user.portfolio_role_summary(self.portfolio), [])
|
||||||
|
|
||||||
@patch("registrar.models.User._has_portfolio_permission")
|
@patch("registrar.models.User._has_portfolio_permission")
|
||||||
def test_has_base_portfolio_permission(self, mock_has_permission):
|
def test_has_view_portfolio_permission(self, mock_has_permission):
|
||||||
mock_has_permission.return_value = True
|
mock_has_permission.return_value = True
|
||||||
|
|
||||||
self.assertTrue(self.user.has_base_portfolio_permission(self.portfolio))
|
self.assertTrue(self.user.has_view_portfolio_permission(self.portfolio))
|
||||||
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.VIEW_PORTFOLIO)
|
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.VIEW_PORTFOLIO)
|
||||||
|
|
||||||
@patch("registrar.models.User._has_portfolio_permission")
|
@patch("registrar.models.User._has_portfolio_permission")
|
||||||
def test_has_edit_org_portfolio_permission(self, mock_has_permission):
|
def test_has_edit_portfolio_permission(self, mock_has_permission):
|
||||||
mock_has_permission.return_value = True
|
mock_has_permission.return_value = True
|
||||||
|
|
||||||
self.assertTrue(self.user.has_edit_org_portfolio_permission(self.portfolio))
|
self.assertTrue(self.user.has_edit_portfolio_permission(self.portfolio))
|
||||||
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.EDIT_PORTFOLIO)
|
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.EDIT_PORTFOLIO)
|
||||||
|
|
||||||
@patch("registrar.models.User._has_portfolio_permission")
|
@patch("registrar.models.User._has_portfolio_permission")
|
||||||
|
@ -1305,20 +1306,6 @@ class TestUser(TestCase):
|
||||||
self.assertTrue(self.user.has_edit_request_portfolio_permission(self.portfolio))
|
self.assertTrue(self.user.has_edit_request_portfolio_permission(self.portfolio))
|
||||||
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.EDIT_REQUESTS)
|
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.EDIT_REQUESTS)
|
||||||
|
|
||||||
@patch("registrar.models.User._has_portfolio_permission")
|
|
||||||
def test_has_view_suborganization_portfolio_permission(self, mock_has_permission):
|
|
||||||
mock_has_permission.return_value = True
|
|
||||||
|
|
||||||
self.assertTrue(self.user.has_view_suborganization_portfolio_permission(self.portfolio))
|
|
||||||
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.VIEW_SUBORGANIZATION)
|
|
||||||
|
|
||||||
@patch("registrar.models.User._has_portfolio_permission")
|
|
||||||
def test_has_edit_suborganization_portfolio_permission(self, mock_has_permission):
|
|
||||||
mock_has_permission.return_value = True
|
|
||||||
|
|
||||||
self.assertTrue(self.user.has_edit_suborganization_portfolio_permission(self.portfolio))
|
|
||||||
mock_has_permission.assert_called_once_with(self.portfolio, UserPortfolioPermissionChoices.EDIT_SUBORGANIZATION)
|
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_check_transition_domains_without_domains_on_login(self):
|
def test_check_transition_domains_without_domains_on_login(self):
|
||||||
"""A user's on_each_login callback does not check transition domains.
|
"""A user's on_each_login callback does not check transition domains.
|
||||||
|
@ -2105,11 +2092,20 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
anything_else="Anything else",
|
anything_else="Anything else",
|
||||||
is_policy_acknowledged=True,
|
is_policy_acknowledged=True,
|
||||||
creator=self.user,
|
creator=self.user,
|
||||||
|
city="fake",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.domain_request.other_contacts.add(other)
|
self.domain_request.other_contacts.add(other)
|
||||||
self.domain_request.current_websites.add(current)
|
self.domain_request.current_websites.add(current)
|
||||||
self.domain_request.alternative_domains.add(alt)
|
self.domain_request.alternative_domains.add(alt)
|
||||||
|
self.wizard = DomainRequestWizard()
|
||||||
|
self.wizard._domain_request = self.domain_request
|
||||||
|
self.wizard.request = Mock(user=self.user, session={})
|
||||||
|
self.wizard.kwargs = {"id": self.domain_request.id}
|
||||||
|
|
||||||
|
# We use both of these flags in the test. In the normal app these are generated normally.
|
||||||
|
# The alternative syntax is adding the decorator to each test.
|
||||||
|
get_waffle_flag_model().objects.get_or_create(name="organization_feature")
|
||||||
|
get_waffle_flag_model().objects.get_or_create(name="organization_requests")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -2124,30 +2120,31 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_federal_complete(self):
|
def test_is_federal_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_federal_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.federal_type = None
|
self.domain_request.federal_type = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_federal_complete())
|
self.domain_request.refresh_from_db()
|
||||||
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_interstate_complete(self):
|
def test_is_interstate_complete(self):
|
||||||
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
|
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
|
||||||
self.domain_request.about_your_organization = "Something something about your organization"
|
self.domain_request.about_your_organization = "Something something about your organization"
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_interstate_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.about_your_organization = None
|
self.domain_request.about_your_organization = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_interstate_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_state_or_territory_complete(self):
|
def test_is_state_or_territory_complete(self):
|
||||||
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
|
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
|
||||||
self.domain_request.is_election_board = True
|
self.domain_request.is_election_board = True
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_state_or_territory_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_election_board = None
|
self.domain_request.is_election_board = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_state_or_territory_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_tribal_complete(self):
|
def test_is_tribal_complete(self):
|
||||||
|
@ -2155,33 +2152,33 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
self.domain_request.tribe_name = "Tribe Name"
|
self.domain_request.tribe_name = "Tribe Name"
|
||||||
self.domain_request.is_election_board = False
|
self.domain_request.is_election_board = False
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_tribal_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_election_board = None
|
self.domain_request.is_election_board = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_tribal_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
self.domain_request.tribe_name = None
|
self.domain_request.tribe_name = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_tribal_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_county_complete(self):
|
def test_is_county_complete(self):
|
||||||
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.COUNTY
|
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.COUNTY
|
||||||
self.domain_request.is_election_board = False
|
self.domain_request.is_election_board = False
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_county_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_election_board = None
|
self.domain_request.is_election_board = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_county_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_city_complete(self):
|
def test_is_city_complete(self):
|
||||||
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.CITY
|
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.CITY
|
||||||
self.domain_request.is_election_board = False
|
self.domain_request.is_election_board = False
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_city_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_election_board = None
|
self.domain_request.is_election_board = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_city_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_special_district_complete(self):
|
def test_is_special_district_complete(self):
|
||||||
|
@ -2189,55 +2186,55 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
self.domain_request.about_your_organization = "Something something about your organization"
|
self.domain_request.about_your_organization = "Something something about your organization"
|
||||||
self.domain_request.is_election_board = False
|
self.domain_request.is_election_board = False
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_special_district_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_election_board = None
|
self.domain_request.is_election_board = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_special_district_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
self.domain_request.about_your_organization = None
|
self.domain_request.about_your_organization = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_special_district_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_organization_name_and_address_complete(self):
|
def test_is_organization_name_and_address_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_organization_name_and_address_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.organization_name = None
|
self.domain_request.organization_name = None
|
||||||
self.domain_request.address_line1 = None
|
self.domain_request.address_line1 = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertTrue(self.domain_request._is_organization_name_and_address_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_senior_official_complete(self):
|
def test_is_senior_official_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_senior_official_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.senior_official = None
|
self.domain_request.senior_official = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_senior_official_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_requested_domain_complete(self):
|
def test_is_requested_domain_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_requested_domain_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.requested_domain = None
|
self.domain_request.requested_domain = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_requested_domain_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_purpose_complete(self):
|
def test_is_purpose_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_purpose_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.purpose = None
|
self.domain_request.purpose = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._is_purpose_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_other_contacts_complete_missing_one_field(self):
|
def test_is_other_contacts_complete_missing_one_field(self):
|
||||||
self.assertTrue(self.domain_request._is_other_contacts_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
contact = self.domain_request.other_contacts.first()
|
contact = self.domain_request.other_contacts.first()
|
||||||
contact.first_name = None
|
contact.first_name = None
|
||||||
contact.save()
|
contact.save()
|
||||||
self.assertFalse(self.domain_request._is_other_contacts_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_other_contacts_complete_all_none(self):
|
def test_is_other_contacts_complete_all_none(self):
|
||||||
self.domain_request.other_contacts.clear()
|
self.domain_request.other_contacts.clear()
|
||||||
self.assertFalse(self.domain_request._is_other_contacts_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_other_contacts_False_and_has_rationale(self):
|
def test_is_other_contacts_False_and_has_rationale(self):
|
||||||
|
@ -2245,7 +2242,7 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
self.domain_request.other_contacts.clear()
|
self.domain_request.other_contacts.clear()
|
||||||
self.domain_request.other_contacts.exists = False
|
self.domain_request.other_contacts.exists = False
|
||||||
self.domain_request.no_other_contacts_rationale = "Some rationale"
|
self.domain_request.no_other_contacts_rationale = "Some rationale"
|
||||||
self.assertTrue(self.domain_request._is_other_contacts_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_other_contacts_False_and_NO_rationale(self):
|
def test_is_other_contacts_False_and_NO_rationale(self):
|
||||||
|
@ -2253,7 +2250,7 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
self.domain_request.other_contacts.clear()
|
self.domain_request.other_contacts.clear()
|
||||||
self.domain_request.other_contacts.exists = False
|
self.domain_request.other_contacts.exists = False
|
||||||
self.domain_request.no_other_contacts_rationale = None
|
self.domain_request.no_other_contacts_rationale = None
|
||||||
self.assertFalse(self.domain_request._is_other_contacts_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_additional_details_complete(self):
|
def test_is_additional_details_complete(self):
|
||||||
|
@ -2457,28 +2454,28 @@ class TestDomainRequestIncomplete(TestCase):
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.domain_request.refresh_from_db()
|
self.domain_request.refresh_from_db()
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.domain_request._is_additional_details_complete(),
|
self.wizard.form_is_complete(),
|
||||||
case["expected"],
|
case["expected"],
|
||||||
msg=f"Failed for case: {case}",
|
msg=f"Failed for case: {case}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_is_policy_acknowledgement_complete(self):
|
def test_is_policy_acknowledgement_complete(self):
|
||||||
self.assertTrue(self.domain_request._is_policy_acknowledgement_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_policy_acknowledged = False
|
self.domain_request.is_policy_acknowledged = False
|
||||||
self.assertTrue(self.domain_request._is_policy_acknowledgement_complete())
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.is_policy_acknowledged = None
|
self.domain_request.is_policy_acknowledged = None
|
||||||
self.assertFalse(self.domain_request._is_policy_acknowledgement_complete())
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_form_complete(self):
|
def test_form_complete(self):
|
||||||
request = self.factory.get("/")
|
request = self.factory.get("/")
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
|
|
||||||
self.assertTrue(self.domain_request._form_complete(request))
|
self.assertTrue(self.wizard.form_is_complete())
|
||||||
self.domain_request.generic_org_type = None
|
self.domain_request.generic_org_type = None
|
||||||
self.domain_request.save()
|
self.domain_request.save()
|
||||||
self.assertFalse(self.domain_request._form_complete(request))
|
self.assertFalse(self.wizard.form_is_complete())
|
||||||
|
|
||||||
|
|
||||||
class TestPortfolio(TestCase):
|
class TestPortfolio(TestCase):
|
||||||
|
|
|
@ -725,7 +725,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
||||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
# @less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_request_data_full(self):
|
def test_domain_request_data_full(self):
|
||||||
"""Tests the full domain request report."""
|
"""Tests the full domain request report."""
|
||||||
# Remove "Submitted at" because we can't guess this immutable, dynamically generated test data
|
# Remove "Submitted at" because we can't guess this immutable, dynamically generated test data
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from registrar.models import User
|
from registrar.models import User
|
||||||
from waffle.testutils import override_flag
|
from waffle.testutils import override_flag
|
||||||
from registrar.utility.waffle import flag_is_active_for_user
|
from waffle.models import get_waffle_flag_model
|
||||||
|
from registrar.utility.waffle import flag_is_active_for_user, flag_is_active_anywhere
|
||||||
|
|
||||||
|
|
||||||
class FlagIsActiveForUserTest(TestCase):
|
class FlagIsActiveForUserTest(TestCase):
|
||||||
|
@ -21,3 +22,40 @@ class FlagIsActiveForUserTest(TestCase):
|
||||||
# Test that the flag is inactive for the user
|
# Test that the flag is inactive for the user
|
||||||
is_active = flag_is_active_for_user(self.user, "test_flag")
|
is_active = flag_is_active_for_user(self.user, "test_flag")
|
||||||
self.assertFalse(is_active)
|
self.assertFalse(is_active)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFlagIsActiveAnywhere(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.user = User.objects.create_user(username="testuser")
|
||||||
|
self.flag_name = "test_flag"
|
||||||
|
|
||||||
|
@override_flag("test_flag", active=True)
|
||||||
|
def test_flag_active_for_everyone(self):
|
||||||
|
"""Test when flag is active for everyone"""
|
||||||
|
is_active = flag_is_active_anywhere("test_flag")
|
||||||
|
self.assertTrue(is_active)
|
||||||
|
|
||||||
|
@override_flag("test_flag", active=False)
|
||||||
|
def test_flag_inactive_for_everyone(self):
|
||||||
|
"""Test when flag is inactive for everyone"""
|
||||||
|
is_active = flag_is_active_anywhere("test_flag")
|
||||||
|
self.assertFalse(is_active)
|
||||||
|
|
||||||
|
def test_flag_active_for_some_users(self):
|
||||||
|
"""Test when flag is active for specific users"""
|
||||||
|
flag, _ = get_waffle_flag_model().objects.get_or_create(name="test_flag")
|
||||||
|
flag.everyone = None
|
||||||
|
flag.save()
|
||||||
|
flag.users.add(self.user)
|
||||||
|
|
||||||
|
is_active = flag_is_active_anywhere("test_flag")
|
||||||
|
self.assertTrue(is_active)
|
||||||
|
|
||||||
|
def test_flag_inactive_with_no_users(self):
|
||||||
|
"""Test when flag has no users and everyone is None"""
|
||||||
|
flag, _ = get_waffle_flag_model().objects.get_or_create(name="test_flag")
|
||||||
|
flag.everyone = None
|
||||||
|
flag.save()
|
||||||
|
|
||||||
|
is_active = flag_is_active_anywhere("test_flag")
|
||||||
|
self.assertFalse(is_active)
|
||||||
|
|
|
@ -2190,7 +2190,7 @@ class TestDomainSuborganization(TestDomainOverview):
|
||||||
self.domain_information.refresh_from_db()
|
self.domain_information.refresh_from_db()
|
||||||
|
|
||||||
# Add portfolio perms to the user object
|
# Add portfolio perms to the user object
|
||||||
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1097,8 +1097,10 @@ class TestPortfolio(WebTest):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_no_permissions(self):
|
def test_main_nav_when_user_has_no_permissions(self):
|
||||||
"""Test the nav contains a link to the no requests page"""
|
"""Test the nav contains a link to the no requests page
|
||||||
|
Also test that members link not present"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
user=self.user, portfolio=self.portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
||||||
)
|
)
|
||||||
|
@ -1118,20 +1120,23 @@ class TestPortfolio(WebTest):
|
||||||
self.assertNotContains(portfolio_landing_page, "basic-nav-section-two")
|
self.assertNotContains(portfolio_landing_page, "basic-nav-section-two")
|
||||||
# link to requests
|
# link to requests
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/requests/')
|
self.assertNotContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create request
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertNotContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_all_permissions(self):
|
def test_main_nav_when_user_has_all_permissions(self):
|
||||||
"""Test the nav contains a dropdown with a link to create and another link to view requests
|
"""Test the nav contains a dropdown with a link to create and another link to view requests
|
||||||
Also test for the existence of the Create a new request btn on the requests page"""
|
Also test for the existence of the Create a new request btn on the requests page
|
||||||
|
Also test for the existence of the members link"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
portfolio=self.portfolio,
|
portfolio=self.portfolio,
|
||||||
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||||
additional_permissions=[UserPortfolioPermissionChoices.EDIT_REQUESTS],
|
|
||||||
)
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
# create and submit a domain request
|
# create and submit a domain request
|
||||||
|
@ -1151,6 +1156,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create
|
||||||
self.assertContains(portfolio_landing_page, 'href="/request/')
|
self.assertContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
requests_page = self.client.get(reverse("domain-requests"))
|
requests_page = self.client.get(reverse("domain-requests"))
|
||||||
|
|
||||||
|
@ -1160,15 +1167,18 @@ class TestPortfolio(WebTest):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
@override_flag("organization_requests", active=True)
|
@override_flag("organization_requests", active=True)
|
||||||
|
@override_flag("organization_members", active=True)
|
||||||
def test_main_nav_when_user_has_view_but_not_edit_permissions(self):
|
def test_main_nav_when_user_has_view_but_not_edit_permissions(self):
|
||||||
"""Test the nav contains a simple link to view requests
|
"""Test the nav contains a simple link to view requests
|
||||||
Also test for the existence of the Create a new request btn on the requests page"""
|
Also test for the existence of the Create a new request btn on the requests page
|
||||||
|
Also test for the existence of members link"""
|
||||||
UserPortfolioPermission.objects.get_or_create(
|
UserPortfolioPermission.objects.get_or_create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
portfolio=self.portfolio,
|
portfolio=self.portfolio,
|
||||||
additional_permissions=[
|
additional_permissions=[
|
||||||
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
UserPortfolioPermissionChoices.VIEW_MEMBERS,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
@ -1189,6 +1199,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
self.assertContains(portfolio_landing_page, 'href="/requests/')
|
||||||
# link to create
|
# link to create
|
||||||
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
self.assertNotContains(portfolio_landing_page, 'href="/request/')
|
||||||
|
# link to members
|
||||||
|
self.assertContains(portfolio_landing_page, 'href="/members/')
|
||||||
|
|
||||||
requests_page = self.client.get(reverse("domain-requests"))
|
requests_page = self.client.get(reverse("domain-requests"))
|
||||||
|
|
||||||
|
|
|
@ -3079,19 +3079,16 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
|
||||||
|
|
||||||
# Create the site and contacts to delete (orphaned)
|
# Create the site and contacts to delete (orphaned)
|
||||||
contact = Contact.objects.create(
|
contact = Contact.objects.create(
|
||||||
first_name="Henry",
|
first_name="Henry", last_name="Mcfakerson", title="test", email="moar@igorville.gov", phone="1234567890"
|
||||||
last_name="Mcfakerson",
|
|
||||||
)
|
)
|
||||||
# Create two non-orphaned contacts
|
# Create two non-orphaned contacts
|
||||||
contact_2 = Contact.objects.create(
|
contact_2 = Contact.objects.create(
|
||||||
first_name="Saturn",
|
first_name="Saturn", last_name="Mars", title="test", email="moar@igorville.gov", phone="1234567890"
|
||||||
last_name="Mars",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Attach a user object to a contact (should not be deleted)
|
# Attach a user object to a contact (should not be deleted)
|
||||||
contact_user, _ = Contact.objects.get_or_create(
|
contact_user, _ = Contact.objects.get_or_create(
|
||||||
first_name="Hank",
|
first_name="Hank", last_name="McFakey", title="test", email="moar@igorville.gov", phone="1234567890"
|
||||||
last_name="McFakey",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
|
@ -3221,6 +3218,37 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
|
||||||
federal_agency.delete()
|
federal_agency.delete()
|
||||||
domain_request.delete()
|
domain_request.delete()
|
||||||
|
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
@override_flag("organization_requests", active=True)
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_unlock_organization_contact_flags_enabled(self):
|
||||||
|
"""Tests unlock_organization_contact when agency exists in a portfolio"""
|
||||||
|
# Create a federal agency
|
||||||
|
federal_agency = FederalAgency.objects.create(agency="Portfolio Agency")
|
||||||
|
|
||||||
|
# Create a portfolio with matching organization name
|
||||||
|
Portfolio.objects.create(
|
||||||
|
creator=self.user, organization_name=federal_agency.agency, federal_agency=federal_agency
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create domain request with the portfolio agency
|
||||||
|
domain_request = completed_domain_request(federal_agency=federal_agency, user=self.user)
|
||||||
|
self.assertFalse(domain_request.unlock_organization_contact())
|
||||||
|
|
||||||
|
@override_flag("organization_feature", active=False)
|
||||||
|
@override_flag("organization_requests", active=False)
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_unlock_organization_contact_flags_disabled(self):
|
||||||
|
"""Tests unlock_organization_contact when organization flags are disabled"""
|
||||||
|
# Create a federal agency
|
||||||
|
federal_agency = FederalAgency.objects.create(agency="Portfolio Agency")
|
||||||
|
|
||||||
|
# Create a portfolio with matching organization name
|
||||||
|
Portfolio.objects.create(creator=self.user, organization_name=federal_agency.agency)
|
||||||
|
|
||||||
|
domain_request = completed_domain_request(federal_agency=federal_agency, user=self.user)
|
||||||
|
self.assertTrue(domain_request.unlock_organization_contact())
|
||||||
|
|
||||||
|
|
||||||
class TestPortfolioDomainRequestViewonly(TestWithUser, WebTest):
|
class TestPortfolioDomainRequestViewonly(TestWithUser, WebTest):
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from waffle.decorators import flag_is_active
|
from waffle.decorators import flag_is_active
|
||||||
|
from waffle.models import get_waffle_flag_model
|
||||||
|
|
||||||
|
|
||||||
def flag_is_active_for_user(user, flag_name):
|
def flag_is_active_for_user(user, flag_name):
|
||||||
|
@ -10,3 +11,21 @@ def flag_is_active_for_user(user, flag_name):
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
request.user = user
|
request.user = user
|
||||||
return flag_is_active(request, flag_name)
|
return flag_is_active(request, flag_name)
|
||||||
|
|
||||||
|
|
||||||
|
def flag_is_active_anywhere(flag_name):
|
||||||
|
"""Checks if the given flag name is active for anyone, anywhere.
|
||||||
|
More specifically, it checks on flag.everyone or flag.users.exists().
|
||||||
|
Does not check self.superuser, self.staff or self.group.
|
||||||
|
|
||||||
|
This function effectively behaves like a switch:
|
||||||
|
If said flag is enabled for someone, somewhere - return true.
|
||||||
|
Otherwise - return false.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
flag = get_waffle_flag_model().get(flag_name)
|
||||||
|
if flag.everyone is None:
|
||||||
|
return flag.users.exists()
|
||||||
|
return flag.everyone
|
||||||
|
except get_waffle_flag_model().DoesNotExist:
|
||||||
|
return False
|
||||||
|
|
|
@ -107,15 +107,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
|
||||||
Step.TRIBAL_GOVERNMENT: lambda self: self.domain_request.tribe_name is not None,
|
Step.TRIBAL_GOVERNMENT: lambda self: self.domain_request.tribe_name is not None,
|
||||||
Step.ORGANIZATION_FEDERAL: lambda self: self.domain_request.federal_type is not None,
|
Step.ORGANIZATION_FEDERAL: lambda self: self.domain_request.federal_type is not None,
|
||||||
Step.ORGANIZATION_ELECTION: lambda self: self.domain_request.is_election_board is not None,
|
Step.ORGANIZATION_ELECTION: lambda self: self.domain_request.is_election_board is not None,
|
||||||
Step.ORGANIZATION_CONTACT: lambda self: (
|
Step.ORGANIZATION_CONTACT: lambda self: self.from_model("unlock_organization_contact", False),
|
||||||
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
|
|
||||||
),
|
|
||||||
Step.ABOUT_YOUR_ORGANIZATION: lambda self: self.domain_request.about_your_organization is not None,
|
Step.ABOUT_YOUR_ORGANIZATION: lambda self: self.domain_request.about_your_organization is not None,
|
||||||
Step.SENIOR_OFFICIAL: lambda self: self.domain_request.senior_official is not None,
|
Step.SENIOR_OFFICIAL: lambda self: self.domain_request.senior_official is not None,
|
||||||
Step.CURRENT_SITES: lambda self: (
|
Step.CURRENT_SITES: lambda self: (
|
||||||
|
@ -123,9 +115,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
|
||||||
),
|
),
|
||||||
Step.DOTGOV_DOMAIN: lambda self: self.domain_request.requested_domain is not None,
|
Step.DOTGOV_DOMAIN: lambda self: self.domain_request.requested_domain is not None,
|
||||||
Step.PURPOSE: lambda self: self.domain_request.purpose is not None,
|
Step.PURPOSE: lambda self: self.domain_request.purpose is not None,
|
||||||
Step.OTHER_CONTACTS: lambda self: (
|
Step.OTHER_CONTACTS: lambda self: self.from_model("unlock_other_contacts", False),
|
||||||
self.domain_request.other_contacts.exists() or self.domain_request.no_other_contacts_rationale is not None
|
|
||||||
),
|
|
||||||
Step.ADDITIONAL_DETAILS: lambda self: (
|
Step.ADDITIONAL_DETAILS: lambda self: (
|
||||||
# Additional details is complete as long as "has anything else" and "has cisa rep" are not None
|
# Additional details is complete as long as "has anything else" and "has cisa rep" are not None
|
||||||
(
|
(
|
||||||
|
@ -434,20 +424,28 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
|
||||||
Queries the DB for a domain request and returns a list of unlocked steps."""
|
Queries the DB for a domain request and returns a list of unlocked steps."""
|
||||||
return [key for key, is_unlocked_checker in self.unlocking_steps.items() if is_unlocked_checker(self)]
|
return [key for key, is_unlocked_checker in self.unlocking_steps.items() if is_unlocked_checker(self)]
|
||||||
|
|
||||||
|
def form_is_complete(self):
|
||||||
|
"""Determines if all required steps in the domain request form are complete.
|
||||||
|
Returns:
|
||||||
|
bool: True if all required steps are complete, False otherwise
|
||||||
|
"""
|
||||||
|
# 1. Get all steps visibly present to the user (required steps)
|
||||||
|
# 2. Return every possible step that is "unlocked" (even hidden, conditional ones)
|
||||||
|
# 3. Narrows down the list to remove hidden conditional steps
|
||||||
|
required_steps = set(self.steps.all)
|
||||||
|
unlockable_steps = {step.value for step in self.db_check_for_unlocking_steps()}
|
||||||
|
unlocked_steps = {step for step in required_steps if step in unlockable_steps}
|
||||||
|
return required_steps == unlocked_steps
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
"""Define context for access on all wizard pages."""
|
"""Define context for access on all wizard pages."""
|
||||||
|
|
||||||
requested_domain_name = None
|
requested_domain_name = None
|
||||||
if self.domain_request.requested_domain is not None:
|
if self.domain_request.requested_domain is not None:
|
||||||
requested_domain_name = self.domain_request.requested_domain.name
|
requested_domain_name = self.domain_request.requested_domain.name
|
||||||
|
|
||||||
context = {}
|
context = {}
|
||||||
|
org_steps_complete = self.form_is_complete()
|
||||||
# Note: we will want to consolidate the non_org_steps_complete check into the same check that
|
if org_steps_complete:
|
||||||
# org_steps_complete is using at some point.
|
|
||||||
non_org_steps_complete = DomainRequest._form_complete(self.domain_request, self.request)
|
|
||||||
org_steps_complete = len(self.db_check_for_unlocking_steps()) == len(self.steps)
|
|
||||||
if (not self.is_portfolio and non_org_steps_complete) or (self.is_portfolio and org_steps_complete):
|
|
||||||
context = {
|
context = {
|
||||||
"form_titles": self.titles,
|
"form_titles": self.titles,
|
||||||
"steps": self.steps,
|
"steps": self.steps,
|
||||||
|
@ -782,7 +780,8 @@ class Review(DomainRequestWizard):
|
||||||
forms = [] # type: ignore
|
forms = [] # type: ignore
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
if DomainRequest._form_complete(self.domain_request, self.request) is False:
|
form_complete = self.form_is_complete()
|
||||||
|
if form_complete is False:
|
||||||
logger.warning("User arrived at review page with an incomplete form.")
|
logger.warning("User arrived at review page with an incomplete form.")
|
||||||
context = super().get_context_data()
|
context = super().get_context_data()
|
||||||
context["Step"] = self.get_step_enum().__members__
|
context["Step"] = self.get_step_enum().__members__
|
||||||
|
|
|
@ -756,7 +756,7 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
||||||
"""Add additional context data to the template."""
|
"""Add additional context data to the template."""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
portfolio = self.request.session.get("portfolio")
|
portfolio = self.request.session.get("portfolio")
|
||||||
context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission(portfolio)
|
context["has_edit_portfolio_permission"] = self.request.user.has_edit_portfolio_permission(portfolio)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
|
|
|
@ -201,17 +201,6 @@ class ExportMembersPortfolio(PortfolioReportsPermission, View):
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
class ExportDataTypeRequests(DomainAndRequestsReportsPermission, View):
|
|
||||||
"""Returns a domain requests report for a given user on the request"""
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
response = HttpResponse(content_type="text/csv")
|
|
||||||
response["Content-Disposition"] = 'attachment; filename="domain-requests.csv"'
|
|
||||||
csv_export.DomainRequestDataType.export_data_to_csv(response, request=request)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator(staff_member_required, name="dispatch")
|
@method_decorator(staff_member_required, name="dispatch")
|
||||||
class ExportDataFull(View):
|
class ExportDataFull(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue