mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-13 13:09:41 +02:00
Merge branch 'main' into hotgov/2493-basic-org-view
This commit is contained in:
commit
ff74e7d6a7
18 changed files with 622 additions and 73 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,8 +7,10 @@ docs/research/data/**
|
||||||
public/
|
public/
|
||||||
credentials*
|
credentials*
|
||||||
|
|
||||||
|
src/certs/
|
||||||
*.pem
|
*.pem
|
||||||
*.crt
|
*.crt
|
||||||
|
*.cer
|
||||||
|
|
||||||
*.bk
|
*.bk
|
||||||
|
|
||||||
|
|
|
@ -358,3 +358,7 @@ Then, copy the variables under the section labled `s3`.
|
||||||
2. Under models, click `Waffle flags`.
|
2. Under models, click `Waffle flags`.
|
||||||
3. Click the `disable_email_sending` record. This should exist by default, if not - create one with that name.
|
3. Click the `disable_email_sending` record. This should exist by default, if not - create one with that name.
|
||||||
4. (Important) Set the field `everyone` to `Yes`. This field overrides all other settings
|
4. (Important) Set the field `everyone` to `Yes`. This field overrides all other settings
|
||||||
|
|
||||||
|
## Request Flow FSM Diagram
|
||||||
|
|
||||||
|
The [.gov Domain Request & Domain Status Digram](https://miro.com/app/board/uXjVMuqbLOk=/?moveToWidget=3458764594819017396&cot=14) visualizes the domain request flow and resulting domain objects.
|
||||||
|
|
|
@ -752,3 +752,67 @@ Example: `cf ssh getgov-za`
|
||||||
| | Parameter | Description |
|
| | Parameter | Description |
|
||||||
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
|
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
|
||||||
| 1 | **emailTo** | Specifies where the email will be emailed. Defaults to help@get.gov on production. |
|
| 1 | **emailTo** | Specifies where the email will be emailed. Defaults to help@get.gov on production. |
|
||||||
|
|
||||||
|
## Populate federal agency initials and FCEB
|
||||||
|
This script adds to the "is_fceb" and "initials" fields on the FederalAgency model. This script expects a CSV of federal CIOs to pull from, which can be sourced from [here](https://docs.google.com/spreadsheets/d/14oXHFpKyUXS5_mDWARPusghGdHCrP67jCleOknaSx38/edit?gid=479328070#gid=479328070).
|
||||||
|
|
||||||
|
### Running on sandboxes
|
||||||
|
|
||||||
|
#### Step 1: Login to CloudFoundry
|
||||||
|
```cf login -a api.fr.cloud.gov --sso```
|
||||||
|
|
||||||
|
#### Step 2: SSH into your environment
|
||||||
|
```cf ssh getgov-{space}```
|
||||||
|
|
||||||
|
Example: `cf ssh getgov-za`
|
||||||
|
|
||||||
|
#### Step 3: Create a shell instance
|
||||||
|
```/tmp/lifecycle/shell```
|
||||||
|
|
||||||
|
#### Step 4: Upload your csv to the desired sandbox
|
||||||
|
[Follow these steps](#use-scp-to-transfer-data-to-sandboxes) to upload the federal_cio csv to a sandbox of your choice.
|
||||||
|
|
||||||
|
#### Step 5: Running the script
|
||||||
|
```./manage.py populate_federal_agency_initials_and_fceb {path_to_CIO_csv}```
|
||||||
|
|
||||||
|
### Running locally
|
||||||
|
|
||||||
|
#### Step 1: Running the script
|
||||||
|
```docker-compose exec app ./manage.py populate_federal_agency_initials_and_fceb {path_to_CIO_csv}```
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
| | Parameter | Description |
|
||||||
|
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
|
||||||
|
| 1 | **federal_cio_csv_path** | Specifies where the federal CIO csv is |
|
||||||
|
|
||||||
|
## Load senior official table
|
||||||
|
This script adds SeniorOfficial records to the related table based off of a CSV. This script expects a CSV of federal CIOs to pull from, which can be sourced from [here](https://docs.google.com/spreadsheets/d/14oXHFpKyUXS5_mDWARPusghGdHCrP67jCleOknaSx38/edit?gid=479328070#gid=479328070).
|
||||||
|
|
||||||
|
### Running on sandboxes
|
||||||
|
|
||||||
|
#### Step 1: Login to CloudFoundry
|
||||||
|
```cf login -a api.fr.cloud.gov --sso```
|
||||||
|
|
||||||
|
#### Step 2: SSH into your environment
|
||||||
|
```cf ssh getgov-{space}```
|
||||||
|
|
||||||
|
Example: `cf ssh getgov-za`
|
||||||
|
|
||||||
|
#### Step 3: Create a shell instance
|
||||||
|
```/tmp/lifecycle/shell```
|
||||||
|
|
||||||
|
#### Step 4: Upload your csv to the desired sandbox
|
||||||
|
[Follow these steps](#use-scp-to-transfer-data-to-sandboxes) to upload the federal_cio csv to a sandbox of your choice.
|
||||||
|
|
||||||
|
#### Step 5: Running the script
|
||||||
|
```./manage.py load_senior_official_table {path_to_CIO_csv}```
|
||||||
|
|
||||||
|
### Running locally
|
||||||
|
|
||||||
|
#### Step 1: Running the script
|
||||||
|
```docker-compose exec app ./manage.py load_senior_official_table {path_to_CIO_csv}```
|
||||||
|
|
||||||
|
##### Parameters
|
||||||
|
| | Parameter | Description |
|
||||||
|
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
|
||||||
|
| 1 | **federal_cio_csv_path** | Specifies where the federal CIO csv is |
|
||||||
|
|
|
@ -1130,10 +1130,9 @@ class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
class SeniorOfficialAdmin(ListHeaderAdmin):
|
class SeniorOfficialAdmin(ListHeaderAdmin):
|
||||||
"""Custom Senior Official Admin class."""
|
"""Custom Senior Official Admin class."""
|
||||||
|
|
||||||
# NOTE: these are just placeholders. Not part of ACs (haven't been defined yet). Update in future tickets.
|
|
||||||
search_fields = ["first_name", "last_name", "email"]
|
search_fields = ["first_name", "last_name", "email"]
|
||||||
search_help_text = "Search by first name, last name or email."
|
search_help_text = "Search by first name, last name or email."
|
||||||
list_display = ["first_name", "last_name", "email"]
|
list_display = ["first_name", "last_name", "email", "federal_agency"]
|
||||||
|
|
||||||
# this ordering effects the ordering of results
|
# this ordering effects the ordering of results
|
||||||
# in autocomplete_fields for Senior Official
|
# in autocomplete_fields for Senior Official
|
||||||
|
@ -2841,6 +2840,9 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
list_display = ("organization_name", "federal_agency", "creator")
|
list_display = ("organization_name", "federal_agency", "creator")
|
||||||
search_fields = ["organization_name"]
|
search_fields = ["organization_name"]
|
||||||
search_help_text = "Search by organization name."
|
search_help_text = "Search by organization name."
|
||||||
|
readonly_fields = [
|
||||||
|
"creator",
|
||||||
|
]
|
||||||
|
|
||||||
# Creates select2 fields (with search bars)
|
# Creates select2 fields (with search bars)
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
|
@ -2863,7 +2865,7 @@ class PortfolioAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
|
|
||||||
if obj.creator is not None:
|
if hasattr(obj, "creator") is False:
|
||||||
# ---- update creator ----
|
# ---- update creator ----
|
||||||
# Set the creator field to the current admin user
|
# Set the creator field to the current admin user
|
||||||
obj.creator = request.user if request.user.is_authenticated else None
|
obj.creator = request.user if request.user.is_authenticated else None
|
||||||
|
|
123
src/registrar/management/commands/load_senior_official_table.py
Normal file
123
src/registrar/management/commands/load_senior_official_table.py
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from registrar.management.commands.utility.terminal_helper import TerminalHelper, TerminalColors
|
||||||
|
from registrar.models import SeniorOfficial, FederalAgency
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
help = """Populates the SeniorOfficial table based off of a given csv"""
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
"""Add command line arguments."""
|
||||||
|
parser.add_argument("federal_cio_csv_path", help="A csv containing information about federal CIOs")
|
||||||
|
|
||||||
|
def handle(self, federal_cio_csv_path, **kwargs):
|
||||||
|
"""Populates the SeniorOfficial table with data given to it through a CSV"""
|
||||||
|
|
||||||
|
# Check if the provided file path is valid.
|
||||||
|
if not os.path.isfile(federal_cio_csv_path):
|
||||||
|
raise argparse.ArgumentTypeError(f"Invalid file path '{federal_cio_csv_path}'")
|
||||||
|
|
||||||
|
TerminalHelper.prompt_for_execution(
|
||||||
|
system_exit_on_terminate=True,
|
||||||
|
info_to_inspect=f"""
|
||||||
|
==Proposed Changes==
|
||||||
|
CSV: {federal_cio_csv_path}
|
||||||
|
|
||||||
|
For each item in this CSV, a SeniorOffical record will be added.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
- If the row is missing SO data - it will not be added.
|
||||||
|
- Given we can add the row, any blank first_name will be replaced with "-".
|
||||||
|
""", # noqa: W291
|
||||||
|
prompt_title="Do you wish to load records into the SeniorOfficial table?",
|
||||||
|
)
|
||||||
|
logger.info("Updating...")
|
||||||
|
|
||||||
|
# Get all existing data.
|
||||||
|
self.existing_senior_officials = SeniorOfficial.objects.all().prefetch_related("federal_agency")
|
||||||
|
self.existing_agencies = FederalAgency.objects.all()
|
||||||
|
|
||||||
|
# Read the CSV
|
||||||
|
self.added_senior_officials = []
|
||||||
|
self.skipped_rows = []
|
||||||
|
with open(federal_cio_csv_path, "r") as requested_file:
|
||||||
|
for row in csv.DictReader(requested_file):
|
||||||
|
# Note: the csv files we have received do not currently have a phone field.
|
||||||
|
# However, we will include it in our kwargs because that is the data we are mapping to
|
||||||
|
# and it seems best to check for the data even if it ends up not being there.
|
||||||
|
so_kwargs = {
|
||||||
|
"first_name": row.get("First Name"),
|
||||||
|
"last_name": row.get("Last Name"),
|
||||||
|
"title": row.get("Role/Position"),
|
||||||
|
"email": row.get("Email"),
|
||||||
|
"phone": row.get("Phone"),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clean the returned data
|
||||||
|
for key, value in so_kwargs.items():
|
||||||
|
if isinstance(value, str):
|
||||||
|
so_kwargs[key] = value.strip()
|
||||||
|
|
||||||
|
# Handle the federal_agency record seperately (db call)
|
||||||
|
agency_name = row.get("Agency").strip() if row.get("Agency") else None
|
||||||
|
if agency_name:
|
||||||
|
so_kwargs["federal_agency"] = self.existing_agencies.filter(agency=agency_name).first()
|
||||||
|
|
||||||
|
# Check if at least one field has a non-empty value
|
||||||
|
if row and any(so_kwargs.values()):
|
||||||
|
# Split into a function: C901 'Command.handle' is too complex.
|
||||||
|
# Doesn't add it to the DB, but just inits a class of SeniorOfficial.
|
||||||
|
self.create_senior_official(so_kwargs)
|
||||||
|
else:
|
||||||
|
self.skipped_rows.append(row)
|
||||||
|
message = f"Skipping row (no data was found): {row}"
|
||||||
|
TerminalHelper.colorful_logger(logger.warning, TerminalColors.YELLOW, message)
|
||||||
|
|
||||||
|
# Bulk create the SO fields
|
||||||
|
if len(self.added_senior_officials) > 0:
|
||||||
|
SeniorOfficial.objects.bulk_create(self.added_senior_officials)
|
||||||
|
|
||||||
|
added_message = f"Added {len(self.added_senior_officials)} records"
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKBLUE, added_message)
|
||||||
|
|
||||||
|
if len(self.skipped_rows) > 0:
|
||||||
|
skipped_message = f"Skipped {len(self.skipped_rows)} records"
|
||||||
|
TerminalHelper.colorful_logger(logger.warning, TerminalColors.MAGENTA, skipped_message)
|
||||||
|
|
||||||
|
def create_senior_official(self, so_kwargs):
|
||||||
|
"""Creates a senior official object from kwargs but does not add it to the DB"""
|
||||||
|
|
||||||
|
# WORKAROUND: Placeholder value for first name,
|
||||||
|
# as not having these makes it impossible to access through DJA.
|
||||||
|
old_first_name = so_kwargs["first_name"]
|
||||||
|
if not so_kwargs["first_name"]:
|
||||||
|
so_kwargs["first_name"] = "-"
|
||||||
|
|
||||||
|
# Create a new SeniorOfficial object
|
||||||
|
new_so = SeniorOfficial(**so_kwargs)
|
||||||
|
|
||||||
|
# Store a variable for the console logger
|
||||||
|
if all([old_first_name, new_so.last_name]):
|
||||||
|
record_display = new_so
|
||||||
|
else:
|
||||||
|
record_display = so_kwargs
|
||||||
|
|
||||||
|
# Before adding this record, check to make sure we aren't adding a duplicate.
|
||||||
|
duplicate_field = self.existing_senior_officials.filter(**so_kwargs).exists()
|
||||||
|
if not duplicate_field:
|
||||||
|
self.added_senior_officials.append(new_so)
|
||||||
|
message = f"Creating record: {record_display}"
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, message)
|
||||||
|
else:
|
||||||
|
# if this field is a duplicate, don't do anything
|
||||||
|
self.skipped_rows.append(new_so)
|
||||||
|
message = f"Skipping add on duplicate record: {record_display}"
|
||||||
|
TerminalHelper.colorful_logger(logger.warning, TerminalColors.YELLOW, message)
|
|
@ -0,0 +1,56 @@
|
||||||
|
import argparse
|
||||||
|
import csv
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from registrar.management.commands.utility.terminal_helper import TerminalHelper, PopulateScriptTemplate, TerminalColors
|
||||||
|
from registrar.models import FederalAgency
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand, PopulateScriptTemplate):
|
||||||
|
|
||||||
|
help = """Populates the initials and fceb fields for FederalAgencies"""
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
"""Add command line arguments."""
|
||||||
|
parser.add_argument("federal_cio_csv_path", help="A csv containing information about federal CIOs")
|
||||||
|
|
||||||
|
def handle(self, federal_cio_csv_path, **kwargs):
|
||||||
|
"""Loops through each FederalAgency object and attempts to update is_fceb and initials"""
|
||||||
|
|
||||||
|
# Check if the provided file path is valid.
|
||||||
|
if not os.path.isfile(federal_cio_csv_path):
|
||||||
|
raise argparse.ArgumentTypeError(f"Invalid file path '{federal_cio_csv_path}'")
|
||||||
|
|
||||||
|
# Returns a dictionary keyed by the agency name containing initials and agency status
|
||||||
|
self.federal_agency_dict = {}
|
||||||
|
with open(federal_cio_csv_path, "r") as requested_file:
|
||||||
|
for row in csv.DictReader(requested_file):
|
||||||
|
agency_name = row.get("Agency")
|
||||||
|
if agency_name:
|
||||||
|
initials = row.get("Initials")
|
||||||
|
agency_status = row.get("Agency Status")
|
||||||
|
self.federal_agency_dict[agency_name.strip()] = (initials, agency_status)
|
||||||
|
|
||||||
|
# Update every federal agency record
|
||||||
|
self.mass_update_records(FederalAgency, {"agency__isnull": False}, ["initials", "is_fceb"])
|
||||||
|
|
||||||
|
def update_record(self, record: FederalAgency):
|
||||||
|
"""For each record, update the initials and is_fceb field if data exists for it"""
|
||||||
|
initials, agency_status = self.federal_agency_dict.get(record.agency)
|
||||||
|
|
||||||
|
record.initials = initials
|
||||||
|
if agency_status and isinstance(agency_status, str) and agency_status.strip().upper() == "FCEB":
|
||||||
|
record.is_fceb = True
|
||||||
|
else:
|
||||||
|
record.is_fceb = False
|
||||||
|
|
||||||
|
message = f"Updating {record} => initials: {initials} | is_fceb: {record.is_fceb}"
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKCYAN, message)
|
||||||
|
|
||||||
|
def should_skip_record(self, record) -> bool:
|
||||||
|
"""Skip record update if there is no data for that particular agency"""
|
||||||
|
return record.agency not in self.federal_agency_dict
|
|
@ -317,6 +317,7 @@ class TerminalHelper:
|
||||||
case _:
|
case _:
|
||||||
logger.info(print_statement)
|
logger.info(print_statement)
|
||||||
|
|
||||||
|
# TODO - "info_to_inspect" should be refactored to "prompt_message"
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prompt_for_execution(system_exit_on_terminate: bool, info_to_inspect: str, prompt_title: str) -> bool:
|
def prompt_for_execution(system_exit_on_terminate: bool, info_to_inspect: str, prompt_title: str) -> bool:
|
||||||
"""Create to reduce code complexity.
|
"""Create to reduce code complexity.
|
||||||
|
@ -373,3 +374,26 @@ class TerminalHelper:
|
||||||
logger.info(f"{TerminalColors.MAGENTA}Writing to file " f" {filepath}..." f"{TerminalColors.ENDC}")
|
logger.info(f"{TerminalColors.MAGENTA}Writing to file " f" {filepath}..." f"{TerminalColors.ENDC}")
|
||||||
with open(f"{filepath}", "w+") as f:
|
with open(f"{filepath}", "w+") as f:
|
||||||
f.write(file_contents)
|
f.write(file_contents)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def colorful_logger(log_level, color, message):
|
||||||
|
"""Adds some color to your log output.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_level: str | Logger.method -> Desired log level. ex: logger.info or "INFO"
|
||||||
|
color: str | TerminalColors -> Output color. ex: TerminalColors.YELLOW or "YELLOW"
|
||||||
|
message: str -> Message to display.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(log_level, str) and hasattr(logger, log_level.lower()):
|
||||||
|
log_method = getattr(logger, log_level.lower())
|
||||||
|
else:
|
||||||
|
log_method = log_level
|
||||||
|
|
||||||
|
if isinstance(color, str) and hasattr(TerminalColors, color.upper()):
|
||||||
|
terminal_color = getattr(TerminalColors, color.upper())
|
||||||
|
else:
|
||||||
|
terminal_color = color
|
||||||
|
|
||||||
|
colored_message = f"{terminal_color}{message}{TerminalColors.ENDC}"
|
||||||
|
log_method(colored_message)
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-08-06 19:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0115_portfolioinvitation"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="federalagency",
|
||||||
|
name="initials",
|
||||||
|
field=models.CharField(blank=True, help_text="Agency initials", max_length=10, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="federalagency",
|
||||||
|
name="is_fceb",
|
||||||
|
field=models.BooleanField(
|
||||||
|
blank=True, help_text="Determines if this agency is FCEB", null=True, verbose_name="FCEB"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="seniorofficial",
|
||||||
|
name="federal_agency",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
help_text="The federal agency this user is associated with",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="so_federal_agency",
|
||||||
|
to="registrar.federalagency",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="seniorofficial",
|
||||||
|
name="first_name",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="first name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="seniorofficial",
|
||||||
|
name="last_name",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="last name"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="seniorofficial",
|
||||||
|
name="title",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="title / role"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -25,6 +25,20 @@ class FederalAgency(TimeStampedModel):
|
||||||
help_text="Federal agency type (executive, judicial, legislative, etc.)",
|
help_text="Federal agency type (executive, judicial, legislative, etc.)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
initials = models.CharField(
|
||||||
|
max_length=10,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Agency initials",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_fceb = models.BooleanField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="FCEB",
|
||||||
|
help_text="Determines if this agency is FCEB",
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.agency}"
|
return f"{self.agency}"
|
||||||
|
|
||||||
|
|
|
@ -12,30 +12,43 @@ class SeniorOfficial(TimeStampedModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
first_name = models.CharField(
|
first_name = models.CharField(
|
||||||
null=False,
|
null=True,
|
||||||
blank=False,
|
blank=True,
|
||||||
verbose_name="first name",
|
verbose_name="first name",
|
||||||
)
|
)
|
||||||
|
|
||||||
last_name = models.CharField(
|
last_name = models.CharField(
|
||||||
null=False,
|
null=True,
|
||||||
blank=False,
|
blank=True,
|
||||||
verbose_name="last name",
|
verbose_name="last name",
|
||||||
)
|
)
|
||||||
|
|
||||||
title = models.CharField(
|
title = models.CharField(
|
||||||
null=False,
|
null=True,
|
||||||
blank=False,
|
blank=True,
|
||||||
verbose_name="title / role",
|
verbose_name="title / role",
|
||||||
)
|
)
|
||||||
|
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
max_length=320,
|
max_length=320,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
federal_agency = models.ForeignKey(
|
||||||
|
"registrar.FederalAgency",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="so_federal_agency",
|
||||||
|
help_text="The federal agency this user is associated with",
|
||||||
|
)
|
||||||
|
|
||||||
def get_formatted_name(self):
|
def get_formatted_name(self):
|
||||||
"""Returns the contact's name in Western order."""
|
"""Returns the contact's name in Western order."""
|
||||||
names = [n for n in [self.first_name, self.last_name] if n]
|
names = [n for n in [self.first_name, self.last_name] if n]
|
||||||
|
|
|
@ -64,11 +64,19 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% url 'domain-org-name-address' pk=domain.id as url %}
|
{% if portfolio %}
|
||||||
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
|
{% comment %} TODO - uncomment in #2352 and add to edit_link
|
||||||
|
{% url 'domain-suborganization' pk=domain.id as url %}
|
||||||
|
{% endcomment %}
|
||||||
|
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link="#" editable=is_editable %}
|
||||||
|
{% else %}
|
||||||
|
{% url 'domain-org-name-address' pk=domain.id as url %}
|
||||||
|
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
|
||||||
|
|
||||||
|
{% url 'domain-senior-official' pk=domain.id as url %}
|
||||||
|
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=is_editable %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% url 'domain-senior-official' pk=domain.id as url %}
|
|
||||||
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=is_editable %}
|
|
||||||
|
|
||||||
{# Conditionally display profile #}
|
{# Conditionally display profile #}
|
||||||
{% if not has_profile_feature_flag %}
|
{% if not has_profile_feature_flag %}
|
||||||
|
|
|
@ -6,8 +6,7 @@
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>Be available </li>
|
<li>Be available </li>
|
||||||
<li>Relate to your organization’s name, location, and/or services </li>
|
<li>Relate to your organization’s name, location, and/or services </li>
|
||||||
<li>Be clear to the general public. Your domain name must not be easily confused
|
<li>Be unlikely to mislead or confuse the general public (even if your domain is only intended for a specific audience) </li>
|
||||||
with other organizations.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
@ -3,16 +3,19 @@
|
||||||
<div class="margin-bottom-4 tablet:margin-bottom-0">
|
<div class="margin-bottom-4 tablet:margin-bottom-0">
|
||||||
<nav aria-label="Domain sections">
|
<nav aria-label="Domain sections">
|
||||||
<ul class="usa-sidenav">
|
<ul class="usa-sidenav">
|
||||||
<li class="usa-sidenav__item">
|
|
||||||
{% url 'domain' pk=domain.id as url %}
|
{% with url_name="domain" %}
|
||||||
<a href="{{ url }}"
|
{% include "includes/domain_sidenav_item.html" with item_text="Domain overview" %}
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
{% endwith %}
|
||||||
>
|
|
||||||
Domain overview
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if is_editable %}
|
{% if is_editable %}
|
||||||
|
|
||||||
|
{% if not portfolio %}
|
||||||
|
{% with url_name="domain-org-name-address" %}
|
||||||
|
{% include "includes/domain_sidenav_item.html" with item_text="Organization name and mailing address" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
<li class="usa-sidenav__item">
|
||||||
{% url 'domain-dns' pk=domain.id as url %}
|
{% url 'domain-dns' pk=domain.id as url %}
|
||||||
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
|
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
|
||||||
|
@ -55,53 +58,32 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
|
||||||
{% url 'domain-org-name-address' pk=domain.id as url %}
|
|
||||||
<a href="{{ url }}"
|
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
|
||||||
>
|
|
||||||
Organization name and mailing address
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
{% if portfolio %}
|
||||||
{% url 'domain-senior-official' pk=domain.id as url %}
|
{% with url="#" %}
|
||||||
<a href="{{ url }}"
|
{% include "includes/domain_sidenav_item.html" with item_text="Suborganization" %}
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
{% endwith %}
|
||||||
>
|
{% else %}
|
||||||
Senior official
|
{% with url_name="domain-senior-official" %}
|
||||||
</a>
|
{% include "includes/domain_sidenav_item.html" with item_text="Senior official" %}
|
||||||
</li>
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if not has_profile_feature_flag %}
|
{% if not has_profile_feature_flag %}
|
||||||
{# Conditionally display profile link in main nav #}
|
{# Conditionally display profile link in main nav #}
|
||||||
<li class="usa-sidenav__item">
|
{% with url_name="domain-your-contact-information" %}
|
||||||
{% url 'domain-your-contact-information' pk=domain.id as url %}
|
{% include "includes/domain_sidenav_item.html" with item_text="Your contact information" %}
|
||||||
<a href="{{ url }}"
|
{% endwith %}
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
|
||||||
>
|
|
||||||
Your contact information
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
{% with url_name="domain-security-email" %}
|
||||||
{% url 'domain-security-email' pk=domain.id as url %}
|
{% include "includes/domain_sidenav_item.html" with item_text="Security email" %}
|
||||||
<a href="{{ url }}"
|
{% endwith %}
|
||||||
{% if request.path == url %}class="usa-current"{% endif %}
|
|
||||||
>
|
{% with url_name="domain-users" %}
|
||||||
Security email
|
{% include "includes/domain_sidenav_item.html" with item_text="Domain managers" %}
|
||||||
</a>
|
{% endwith %}
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="usa-sidenav__item">
|
|
||||||
{% url 'domain-users' pk=domain.id as url %}
|
|
||||||
<a href="{{ url }}"
|
|
||||||
{% if request.path|startswith:url %}class="usa-current"{% endif %}
|
|
||||||
>
|
|
||||||
Domain managers
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
10
src/registrar/templates/includes/domain_sidenav_item.html
Normal file
10
src/registrar/templates/includes/domain_sidenav_item.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<li class="usa-sidenav__item">
|
||||||
|
{% if url_name %}
|
||||||
|
{% url url_name pk=domain.id as url %}
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ url }}"
|
||||||
|
{% if request.path == url %}class="usa-current"{% endif %}
|
||||||
|
>
|
||||||
|
{{ item_text }}
|
||||||
|
</a>
|
||||||
|
</li>
|
5
src/registrar/tests/data/fake_federal_cio.csv
Normal file
5
src/registrar/tests/data/fake_federal_cio.csv
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Contact Status,Email,First Name,Last Name,Role/Position,Initials,Agency,Agency Status,Notes,Active?,Modified,Created
|
||||||
|
Unconfirmed,fakemrfake@igorville.gov,Jan,Uary,CIO,ABMC,American Battle Monuments Commission,FCEB,,,,,
|
||||||
|
Import,reggie.ronald@igorville.gov,Reggie,Ronald,CIO,ACHP,Advisory Council on Historic Preservation,FCEB,Some notes field,,,
|
||||||
|
,,,,,AMTRAK,,,,,,
|
||||||
|
,,,,,KC,John F. Kennedy Center for Performing Arts,,,,,
|
|
|
@ -2,6 +2,7 @@ import copy
|
||||||
from datetime import date, datetime, time
|
from datetime import date, datetime, time
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
|
from registrar.models.senior_official import SeniorOfficial
|
||||||
from registrar.utility.constants import BranchChoices
|
from registrar.utility.constants import BranchChoices
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
@ -1285,3 +1286,125 @@ class TestTransferFederalAgencyType(TestCase):
|
||||||
|
|
||||||
# We don't expect this field to be updated (as it has duplicate data)
|
# We don't expect this field to be updated (as it has duplicate data)
|
||||||
self.assertEqual(self.gov_admin.federal_type, None)
|
self.assertEqual(self.gov_admin.federal_type, None)
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoadSeniorOfficialTable(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.csv_path = "registrar/tests/data/fake_federal_cio.csv"
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
SeniorOfficial.objects.all().delete()
|
||||||
|
FederalAgency.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def run_load_senior_official_table(self):
|
||||||
|
with patch(
|
||||||
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
call_command("load_senior_official_table", self.csv_path)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_load_senior_official_table(self):
|
||||||
|
"""Ensures that running the senior official script creates the data we expect"""
|
||||||
|
# Get test FederalAgency objects
|
||||||
|
abmc, _ = FederalAgency.objects.get_or_create(agency="American Battle Monuments Commission")
|
||||||
|
achp, _ = FederalAgency.objects.get_or_create(agency="Advisory Council on Historic Preservation")
|
||||||
|
|
||||||
|
# run the script
|
||||||
|
self.run_load_senior_official_table()
|
||||||
|
|
||||||
|
# Check the data returned by the script
|
||||||
|
jan_uary = SeniorOfficial.objects.get(first_name="Jan", last_name="Uary")
|
||||||
|
self.assertEqual(jan_uary.title, "CIO")
|
||||||
|
self.assertEqual(jan_uary.email, "fakemrfake@igorville.gov")
|
||||||
|
self.assertEqual(jan_uary.federal_agency, abmc)
|
||||||
|
|
||||||
|
reggie_ronald = SeniorOfficial.objects.get(first_name="Reggie", last_name="Ronald")
|
||||||
|
self.assertEqual(reggie_ronald.title, "CIO")
|
||||||
|
self.assertEqual(reggie_ronald.email, "reggie.ronald@igorville.gov")
|
||||||
|
self.assertEqual(reggie_ronald.federal_agency, achp)
|
||||||
|
|
||||||
|
# Two should be created in total
|
||||||
|
self.assertEqual(SeniorOfficial.objects.count(), 2)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_load_senior_official_table_duplicate_entry(self):
|
||||||
|
"""Ensures that duplicate data won't be created"""
|
||||||
|
# Create a SeniorOfficial that matches one in the CSV
|
||||||
|
abmc, _ = FederalAgency.objects.get_or_create(agency="American Battle Monuments Commission")
|
||||||
|
SeniorOfficial.objects.create(
|
||||||
|
first_name="Jan", last_name="Uary", title="CIO", email="fakemrfake@igorville.gov", federal_agency=abmc
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(SeniorOfficial.objects.count(), 1)
|
||||||
|
|
||||||
|
# run the script
|
||||||
|
self.run_load_senior_official_table()
|
||||||
|
|
||||||
|
# Check if only one new SeniorOfficial object was created
|
||||||
|
self.assertEqual(SeniorOfficial.objects.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPopulateFederalAgencyInitialsAndFceb(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.csv_path = "registrar/tests/data/fake_federal_cio.csv"
|
||||||
|
|
||||||
|
# Create test FederalAgency objects
|
||||||
|
self.agency1, _ = FederalAgency.objects.get_or_create(agency="American Battle Monuments Commission")
|
||||||
|
self.agency2, _ = FederalAgency.objects.get_or_create(agency="Advisory Council on Historic Preservation")
|
||||||
|
self.agency3, _ = FederalAgency.objects.get_or_create(agency="AMTRAK")
|
||||||
|
self.agency4, _ = FederalAgency.objects.get_or_create(agency="John F. Kennedy Center for Performing Arts")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
SeniorOfficial.objects.all().delete()
|
||||||
|
FederalAgency.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def run_populate_federal_agency_initials_and_fceb(self):
|
||||||
|
with patch(
|
||||||
|
"registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
call_command("populate_federal_agency_initials_and_fceb", self.csv_path)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_populate_federal_agency_initials_and_fceb(self):
|
||||||
|
"""Ensures that the script generates the data we want"""
|
||||||
|
self.run_populate_federal_agency_initials_and_fceb()
|
||||||
|
|
||||||
|
# Refresh the objects from the database
|
||||||
|
self.agency1.refresh_from_db()
|
||||||
|
self.agency2.refresh_from_db()
|
||||||
|
self.agency3.refresh_from_db()
|
||||||
|
self.agency4.refresh_from_db()
|
||||||
|
|
||||||
|
# Check if FederalAgency objects were updated correctly
|
||||||
|
self.assertEqual(self.agency1.initials, "ABMC")
|
||||||
|
self.assertTrue(self.agency1.is_fceb)
|
||||||
|
|
||||||
|
self.assertEqual(self.agency2.initials, "ACHP")
|
||||||
|
self.assertTrue(self.agency2.is_fceb)
|
||||||
|
|
||||||
|
# We expect that this field doesn't have any data,
|
||||||
|
# as none is specified in the CSV
|
||||||
|
self.assertIsNone(self.agency3.initials)
|
||||||
|
self.assertIsNone(self.agency3.is_fceb)
|
||||||
|
|
||||||
|
self.assertEqual(self.agency4.initials, "KC")
|
||||||
|
self.assertFalse(self.agency4.is_fceb)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_populate_federal_agency_initials_and_fceb_missing_agency(self):
|
||||||
|
"""A test to ensure that the script doesn't modify unrelated fields"""
|
||||||
|
# Add a FederalAgency that's not in the CSV
|
||||||
|
missing_agency = FederalAgency.objects.create(agency="Missing Agency")
|
||||||
|
|
||||||
|
self.run_populate_federal_agency_initials_and_fceb()
|
||||||
|
|
||||||
|
# Verify that the missing agency was not updated
|
||||||
|
missing_agency.refresh_from_db()
|
||||||
|
self.assertIsNone(missing_agency.initials)
|
||||||
|
self.assertIsNone(missing_agency.is_fceb)
|
||||||
|
|
|
@ -4,10 +4,9 @@ from unittest.mock import MagicMock, ANY, patch
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from waffle.testutils import override_flag
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
from registrar.models.portfolio import Portfolio
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
|
||||||
from .common import MockEppLib, MockSESClient, create_user # type: ignore
|
from .common import MockEppLib, MockSESClient, create_user # type: ignore
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
@ -35,6 +34,8 @@ from registrar.models import (
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
User,
|
User,
|
||||||
FederalAgency,
|
FederalAgency,
|
||||||
|
Portfolio,
|
||||||
|
Suborganization,
|
||||||
)
|
)
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -140,6 +141,7 @@ class TestWithDomainPermissions(TestWithUser):
|
||||||
Host.objects.all().delete()
|
Host.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
Suborganization.objects.all().delete()
|
||||||
Portfolio.objects.all().delete()
|
Portfolio.objects.all().delete()
|
||||||
except ValueError: # pass if already deleted
|
except ValueError: # pass if already deleted
|
||||||
pass
|
pass
|
||||||
|
@ -1126,7 +1128,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
|
||||||
def test_domain_senior_official(self):
|
def test_domain_senior_official(self):
|
||||||
"""Can load domain's senior official page."""
|
"""Can load domain's senior official page."""
|
||||||
page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
|
page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
|
||||||
self.assertContains(page, "Senior official", count=13)
|
self.assertContains(page, "Senior official", count=14)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_senior_official_content(self):
|
def test_domain_senior_official_content(self):
|
||||||
|
@ -1346,7 +1348,7 @@ class TestDomainOrganization(TestDomainOverview):
|
||||||
"""Can load domain's org name and mailing address page."""
|
"""Can load domain's org name and mailing address page."""
|
||||||
page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id}))
|
page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id}))
|
||||||
# once on the sidebar, once in the page title, once as H1
|
# once on the sidebar, once in the page title, once as H1
|
||||||
self.assertContains(page, "Organization name and mailing address", count=3)
|
self.assertContains(page, "Organization name and mailing address", count=4)
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_org_name_address_content(self):
|
def test_domain_org_name_address_content(self):
|
||||||
|
@ -1521,6 +1523,52 @@ class TestDomainOrganization(TestDomainOverview):
|
||||||
self.assertNotEqual(self.domain_information.federal_agency, new_value)
|
self.assertNotEqual(self.domain_information.federal_agency, new_value)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainSuborganization(TestDomainOverview):
|
||||||
|
"""Tests the Suborganization page for portfolio users"""
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@override_flag("organization_feature", active=True)
|
||||||
|
def test_has_suborganization_field_on_overview_with_flag(self):
|
||||||
|
"""Ensures that the suborganization field is visible
|
||||||
|
and displays correctly on the domain overview page"""
|
||||||
|
|
||||||
|
# Create a portfolio
|
||||||
|
portfolio = Portfolio.objects.create(creator=self.user, organization_name="Ice Cream")
|
||||||
|
suborg = Suborganization.objects.create(portfolio=portfolio, name="Vanilla")
|
||||||
|
|
||||||
|
# Add the portfolio to the domain_information object
|
||||||
|
self.domain_information.portfolio = portfolio
|
||||||
|
|
||||||
|
# Add a organization_name to test if the old value still displays
|
||||||
|
self.domain_information.organization_name = "Broccoli"
|
||||||
|
self.domain_information.save()
|
||||||
|
self.domain_information.refresh_from_db()
|
||||||
|
|
||||||
|
# Add portfolio perms to the user object
|
||||||
|
self.user.portfolio = portfolio
|
||||||
|
self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_PORTFOLIO]
|
||||||
|
self.user.save()
|
||||||
|
self.user.refresh_from_db()
|
||||||
|
|
||||||
|
# Navigate to the domain overview page
|
||||||
|
page = self.app.get(reverse("domain", kwargs={"pk": self.domain.id}))
|
||||||
|
|
||||||
|
# Test for the title change
|
||||||
|
self.assertContains(page, "Suborganization")
|
||||||
|
self.assertNotContains(page, "Organization name")
|
||||||
|
|
||||||
|
# Test for the good value
|
||||||
|
self.assertContains(page, "Ice Cream")
|
||||||
|
|
||||||
|
# Test for the bad value
|
||||||
|
self.assertNotContains(page, "Broccoli")
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
self.domain_information.delete()
|
||||||
|
suborg.delete()
|
||||||
|
portfolio.delete()
|
||||||
|
|
||||||
|
|
||||||
class TestDomainContactInformation(TestDomainOverview):
|
class TestDomainContactInformation(TestDomainOverview):
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_your_contact_information(self):
|
def test_domain_your_contact_information(self):
|
||||||
|
|
|
@ -23,8 +23,8 @@ from registrar.models import (
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
User,
|
User,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
|
PublicContact,
|
||||||
)
|
)
|
||||||
from registrar.models.public_contact import PublicContact
|
|
||||||
from registrar.utility.enums import DefaultEmail
|
from registrar.utility.enums import DefaultEmail
|
||||||
from registrar.utility.errors import (
|
from registrar.utility.errors import (
|
||||||
GenericError,
|
GenericError,
|
||||||
|
@ -231,6 +231,16 @@ class DomainOrgNameAddressView(DomainFormBaseView):
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Override for the has_permission class to exclude portfolio users"""
|
||||||
|
|
||||||
|
# Org users shouldn't have access to this page
|
||||||
|
is_org_user = self.request.user.is_org_user(self.request)
|
||||||
|
if self.request.user.portfolio and is_org_user:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return super().has_permission()
|
||||||
|
|
||||||
|
|
||||||
class DomainSeniorOfficialView(DomainFormBaseView):
|
class DomainSeniorOfficialView(DomainFormBaseView):
|
||||||
"""Domain senior official editing view."""
|
"""Domain senior official editing view."""
|
||||||
|
@ -248,7 +258,6 @@ class DomainSeniorOfficialView(DomainFormBaseView):
|
||||||
domain_info = self.get_domain_info_from_domain()
|
domain_info = self.get_domain_info_from_domain()
|
||||||
invalid_fields = [DomainRequest.OrganizationChoices.FEDERAL, DomainRequest.OrganizationChoices.TRIBAL]
|
invalid_fields = [DomainRequest.OrganizationChoices.FEDERAL, DomainRequest.OrganizationChoices.TRIBAL]
|
||||||
is_federal_or_tribal = domain_info and (domain_info.generic_org_type in invalid_fields)
|
is_federal_or_tribal = domain_info and (domain_info.generic_org_type in invalid_fields)
|
||||||
|
|
||||||
form_kwargs["disable_fields"] = is_federal_or_tribal
|
form_kwargs["disable_fields"] = is_federal_or_tribal
|
||||||
return form_kwargs
|
return form_kwargs
|
||||||
|
|
||||||
|
@ -276,6 +285,16 @@ class DomainSeniorOfficialView(DomainFormBaseView):
|
||||||
# superclass has the redirect
|
# superclass has the redirect
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def has_permission(self):
|
||||||
|
"""Override for the has_permission class to exclude portfolio users"""
|
||||||
|
|
||||||
|
# Org users shouldn't have access to this page
|
||||||
|
is_org_user = self.request.user.is_org_user(self.request)
|
||||||
|
if self.request.user.portfolio and is_org_user:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return super().has_permission()
|
||||||
|
|
||||||
|
|
||||||
class DomainDNSView(DomainBaseView):
|
class DomainDNSView(DomainBaseView):
|
||||||
"""DNS Information View."""
|
"""DNS Information View."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue