mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-28 16:29:54 +02:00
Merge remote-tracking branch 'origin' into dk/1839-domain-request-form
This commit is contained in:
commit
f9360bec20
15 changed files with 789 additions and 880 deletions
|
@ -17,6 +17,7 @@ from dateutil.relativedelta import relativedelta # type: ignore
|
||||||
from epplibwrapper.errors import ErrorCode, RegistryError
|
from epplibwrapper.errors import ErrorCode, RegistryError
|
||||||
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
|
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
|
||||||
from registrar.utility import csv_export
|
from registrar.utility import csv_export
|
||||||
|
from registrar.utility.errors import FSMApplicationError, FSMErrorCodes
|
||||||
from registrar.views.utility.mixins import OrderableFieldsMixin
|
from registrar.views.utility.mixins import OrderableFieldsMixin
|
||||||
from django.contrib.admin.views.main import ORDER_VAR
|
from django.contrib.admin.views.main import ORDER_VAR
|
||||||
from registrar.widgets import NoAutocompleteFilteredSelectMultiple
|
from registrar.widgets import NoAutocompleteFilteredSelectMultiple
|
||||||
|
@ -93,9 +94,14 @@ class DomainRequestAdminForm(forms.ModelForm):
|
||||||
# first option in status transitions is current state
|
# first option in status transitions is current state
|
||||||
available_transitions = [(current_state, domain_request.get_status_display())]
|
available_transitions = [(current_state, domain_request.get_status_display())]
|
||||||
|
|
||||||
transitions = get_available_FIELD_transitions(
|
if domain_request.investigator is not None:
|
||||||
domain_request, models.DomainRequest._meta.get_field("status")
|
transitions = get_available_FIELD_transitions(
|
||||||
)
|
domain_request, models.DomainRequest._meta.get_field("status")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
transitions = self.get_custom_field_transitions(
|
||||||
|
domain_request, models.DomainRequest._meta.get_field("status")
|
||||||
|
)
|
||||||
|
|
||||||
for transition in transitions:
|
for transition in transitions:
|
||||||
available_transitions.append((transition.target, transition.target.label))
|
available_transitions.append((transition.target, transition.target.label))
|
||||||
|
@ -106,6 +112,73 @@ class DomainRequestAdminForm(forms.ModelForm):
|
||||||
if not domain_request.creator.is_restricted():
|
if not domain_request.creator.is_restricted():
|
||||||
self.fields["status"].widget.choices = available_transitions
|
self.fields["status"].widget.choices = available_transitions
|
||||||
|
|
||||||
|
def get_custom_field_transitions(self, instance, field):
|
||||||
|
"""Custom implementation of get_available_FIELD_transitions
|
||||||
|
in the FSM. Allows us to still display fields filtered out by a condition."""
|
||||||
|
curr_state = field.get_state(instance)
|
||||||
|
transitions = field.transitions[instance.__class__]
|
||||||
|
|
||||||
|
for name, transition in transitions.items():
|
||||||
|
meta = transition._django_fsm
|
||||||
|
if meta.has_transition(curr_state):
|
||||||
|
yield meta.get_transition(curr_state)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Override of the default clean on the form.
|
||||||
|
This is so we can inject custom form-level error messages.
|
||||||
|
"""
|
||||||
|
# clean is called from clean_forms, which is called from is_valid
|
||||||
|
# after clean_fields. it is used to determine form level errors.
|
||||||
|
# is_valid is typically called from view during a post
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
status = cleaned_data.get("status")
|
||||||
|
investigator = cleaned_data.get("investigator")
|
||||||
|
|
||||||
|
# Get the old status
|
||||||
|
initial_status = self.initial.get("status", None)
|
||||||
|
|
||||||
|
# We only care about investigator when in these statuses
|
||||||
|
checked_statuses = [
|
||||||
|
DomainRequest.DomainRequestStatus.APPROVED,
|
||||||
|
DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
|
||||||
|
DomainRequest.DomainRequestStatus.REJECTED,
|
||||||
|
DomainRequest.DomainRequestStatus.INELIGIBLE,
|
||||||
|
]
|
||||||
|
|
||||||
|
# If a status change occured, check for validity
|
||||||
|
if status != initial_status and status in checked_statuses:
|
||||||
|
# Checks the "investigators" field for validity.
|
||||||
|
# That field must obey certain conditions when an domain request is approved.
|
||||||
|
# Will call "add_error" if any issues are found.
|
||||||
|
self._check_for_valid_investigator(investigator)
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def _check_for_valid_investigator(self, investigator) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the investigator field is not none, and is staff.
|
||||||
|
Adds form errors on failure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
is_valid = False
|
||||||
|
|
||||||
|
# Check if an investigator is assigned. No approval is possible without one.
|
||||||
|
error_message = None
|
||||||
|
if investigator is None:
|
||||||
|
# Lets grab the error message from a common location
|
||||||
|
error_message = FSMApplicationError.get_error_message(FSMErrorCodes.NO_INVESTIGATOR)
|
||||||
|
elif not investigator.is_staff:
|
||||||
|
error_message = FSMApplicationError.get_error_message(FSMErrorCodes.INVESTIGATOR_NOT_STAFF)
|
||||||
|
else:
|
||||||
|
is_valid = True
|
||||||
|
|
||||||
|
if error_message is not None:
|
||||||
|
self.add_error("investigator", error_message)
|
||||||
|
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
|
||||||
# Based off of this excellent example: https://djangosnippets.org/snippets/10471/
|
# Based off of this excellent example: https://djangosnippets.org/snippets/10471/
|
||||||
class MultiFieldSortableChangeList(admin.views.main.ChangeList):
|
class MultiFieldSortableChangeList(admin.views.main.ChangeList):
|
||||||
|
@ -1046,72 +1119,24 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
if obj and obj.creator.status != models.User.RESTRICTED:
|
"""Custom save_model definition that handles edge cases"""
|
||||||
if change: # Check if the domain request is being edited
|
|
||||||
# Get the original domain request from the database
|
|
||||||
original_obj = models.DomainRequest.objects.get(pk=obj.pk)
|
|
||||||
|
|
||||||
if (
|
# == Check that the obj is in a valid state == #
|
||||||
obj
|
|
||||||
and original_obj.status == models.DomainRequest.DomainRequestStatus.APPROVED
|
|
||||||
and obj.status != models.DomainRequest.DomainRequestStatus.APPROVED
|
|
||||||
and not obj.domain_is_not_active()
|
|
||||||
):
|
|
||||||
# If an admin tried to set an approved domain request to
|
|
||||||
# another status and the related domain is already
|
|
||||||
# active, shortcut the action and throw a friendly
|
|
||||||
# error message. This action would still not go through
|
|
||||||
# shortcut or not as the rules are duplicated on the model,
|
|
||||||
# but the error would be an ugly Django error screen.
|
|
||||||
|
|
||||||
# Clear the success message
|
# If obj is none, something went very wrong.
|
||||||
messages.set_level(request, messages.ERROR)
|
# The form should have blocked this, so lets forbid it.
|
||||||
|
if not obj:
|
||||||
|
logger.error(f"Invalid value for obj ({obj})")
|
||||||
|
messages.set_level(request, messages.ERROR)
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
"Could not save DomainRequest. Something went wrong.",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
messages.error(
|
# If the user is restricted or we're saving an invalid model,
|
||||||
request,
|
# forbid this action.
|
||||||
"This action is not permitted. The domain is already active.",
|
if not obj or obj.creator.status == models.User.RESTRICTED:
|
||||||
)
|
|
||||||
|
|
||||||
elif (
|
|
||||||
obj and obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason
|
|
||||||
):
|
|
||||||
# This condition should never be triggered.
|
|
||||||
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason)
|
|
||||||
# because we clean up the rejection reason in the transition in the model.
|
|
||||||
|
|
||||||
# Clear the success message
|
|
||||||
messages.set_level(request, messages.ERROR)
|
|
||||||
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
"A rejection reason is required.",
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
if obj.status != original_obj.status:
|
|
||||||
status_method_mapping = {
|
|
||||||
models.DomainRequest.DomainRequestStatus.STARTED: None,
|
|
||||||
models.DomainRequest.DomainRequestStatus.SUBMITTED: obj.submit,
|
|
||||||
models.DomainRequest.DomainRequestStatus.IN_REVIEW: obj.in_review,
|
|
||||||
models.DomainRequest.DomainRequestStatus.ACTION_NEEDED: obj.action_needed,
|
|
||||||
models.DomainRequest.DomainRequestStatus.APPROVED: obj.approve,
|
|
||||||
models.DomainRequest.DomainRequestStatus.WITHDRAWN: obj.withdraw,
|
|
||||||
models.DomainRequest.DomainRequestStatus.REJECTED: obj.reject,
|
|
||||||
models.DomainRequest.DomainRequestStatus.INELIGIBLE: (obj.reject_with_prejudice),
|
|
||||||
}
|
|
||||||
selected_method = status_method_mapping.get(obj.status)
|
|
||||||
if selected_method is None:
|
|
||||||
logger.warning("Unknown status selected in django admin")
|
|
||||||
else:
|
|
||||||
# This is an fsm in model which will throw an error if the
|
|
||||||
# transition condition is violated, so we roll back the
|
|
||||||
# status to what it was before the admin user changed it and
|
|
||||||
# let the fsm method set it.
|
|
||||||
obj.status = original_obj.status
|
|
||||||
selected_method()
|
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
|
||||||
else:
|
|
||||||
# Clear the success message
|
# Clear the success message
|
||||||
messages.set_level(request, messages.ERROR)
|
messages.set_level(request, messages.ERROR)
|
||||||
|
|
||||||
|
@ -1120,6 +1145,117 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"This action is not permitted for domain requests with a restricted creator.",
|
"This action is not permitted for domain requests with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
# == Check if we're making a change or not == #
|
||||||
|
|
||||||
|
# If we're not making a change (adding a record), run save model as we do normally
|
||||||
|
if not change:
|
||||||
|
return super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
# == Handle non-status changes == #
|
||||||
|
|
||||||
|
# Get the original domain request from the database.
|
||||||
|
original_obj = models.DomainRequest.objects.get(pk=obj.pk)
|
||||||
|
if obj.status == original_obj.status:
|
||||||
|
# If the status hasn't changed, let the base function take care of it
|
||||||
|
return super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
# == Handle status changes == #
|
||||||
|
|
||||||
|
# Run some checks on the current object for invalid status changes
|
||||||
|
obj, should_save = self._handle_status_change(request, obj, original_obj)
|
||||||
|
|
||||||
|
# We should only save if we don't display any errors in the step above.
|
||||||
|
if should_save:
|
||||||
|
return super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
def _handle_status_change(self, request, obj, original_obj):
|
||||||
|
"""
|
||||||
|
Checks for various conditions when a status change is triggered.
|
||||||
|
In the event that it is valid, the status will be mapped to
|
||||||
|
the appropriate method.
|
||||||
|
|
||||||
|
In the event that we should not status change, an error message
|
||||||
|
will be displayed.
|
||||||
|
|
||||||
|
Returns a tuple: (obj: DomainRequest, should_proceed: bool)
|
||||||
|
"""
|
||||||
|
|
||||||
|
should_proceed = True
|
||||||
|
error_message = None
|
||||||
|
|
||||||
|
# Get the method that should be run given the status
|
||||||
|
selected_method = self.get_status_method_mapping(obj)
|
||||||
|
if selected_method is None:
|
||||||
|
logger.warning("Unknown status selected in django admin")
|
||||||
|
|
||||||
|
# If the status is not mapped properly, saving could cause
|
||||||
|
# weird issues down the line. Instead, we should block this.
|
||||||
|
should_proceed = False
|
||||||
|
return should_proceed
|
||||||
|
|
||||||
|
request_is_not_approved = obj.status != models.DomainRequest.DomainRequestStatus.APPROVED
|
||||||
|
if request_is_not_approved and not obj.domain_is_not_active():
|
||||||
|
# If an admin tried to set an approved domain request to
|
||||||
|
# another status and the related domain is already
|
||||||
|
# active, shortcut the action and throw a friendly
|
||||||
|
# error message. This action would still not go through
|
||||||
|
# shortcut or not as the rules are duplicated on the model,
|
||||||
|
# but the error would be an ugly Django error screen.
|
||||||
|
error_message = "This action is not permitted. The domain is already active."
|
||||||
|
elif obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason:
|
||||||
|
# This condition should never be triggered.
|
||||||
|
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason)
|
||||||
|
# because we clean up the rejection reason in the transition in the model.
|
||||||
|
error_message = "A rejection reason is required."
|
||||||
|
else:
|
||||||
|
# This is an fsm in model which will throw an error if the
|
||||||
|
# transition condition is violated, so we roll back the
|
||||||
|
# status to what it was before the admin user changed it and
|
||||||
|
# let the fsm method set it.
|
||||||
|
obj.status = original_obj.status
|
||||||
|
|
||||||
|
# Try to perform the status change.
|
||||||
|
# Catch FSMApplicationError's and return the message,
|
||||||
|
# as these are typically user errors.
|
||||||
|
try:
|
||||||
|
selected_method()
|
||||||
|
except FSMApplicationError as err:
|
||||||
|
logger.warning(f"An error encountered when trying to change status: {err}")
|
||||||
|
error_message = err.message
|
||||||
|
|
||||||
|
if error_message is not None:
|
||||||
|
# Clear the success message
|
||||||
|
messages.set_level(request, messages.ERROR)
|
||||||
|
# Display the error
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
error_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
# If an error message exists, we shouldn't proceed
|
||||||
|
should_proceed = False
|
||||||
|
|
||||||
|
return (obj, should_proceed)
|
||||||
|
|
||||||
|
def get_status_method_mapping(self, domain_request):
|
||||||
|
"""Returns what method should be ran given an domain request object"""
|
||||||
|
# Define a per-object mapping
|
||||||
|
status_method_mapping = {
|
||||||
|
models.DomainRequest.DomainRequestStatus.STARTED: None,
|
||||||
|
models.DomainRequest.DomainRequestStatus.SUBMITTED: domain_request.submit,
|
||||||
|
models.DomainRequest.DomainRequestStatus.IN_REVIEW: domain_request.in_review,
|
||||||
|
models.DomainRequest.DomainRequestStatus.ACTION_NEEDED: domain_request.action_needed,
|
||||||
|
models.DomainRequest.DomainRequestStatus.APPROVED: domain_request.approve,
|
||||||
|
models.DomainRequest.DomainRequestStatus.WITHDRAWN: domain_request.withdraw,
|
||||||
|
models.DomainRequest.DomainRequestStatus.REJECTED: domain_request.reject,
|
||||||
|
models.DomainRequest.DomainRequestStatus.INELIGIBLE: (domain_request.reject_with_prejudice),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grab the method
|
||||||
|
return status_method_mapping.get(domain_request.status, None)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Set the read-only state on form elements.
|
"""Set the read-only state on form elements.
|
||||||
We have 2 conditions that determine which fields are read-only:
|
We have 2 conditions that determine which fields are read-only:
|
||||||
|
|
|
@ -281,11 +281,6 @@ div#content > h2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hides the "clear" button on autocomplete, as we already have one to use
|
|
||||||
.select2-selection__clear {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fixes a display issue where the list was entirely white, or had too much whitespace
|
// Fixes a display issue where the list was entirely white, or had too much whitespace
|
||||||
.select2-dropdown {
|
.select2-dropdown {
|
||||||
display: inline-grid !important;
|
display: inline-grid !important;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
User,
|
User,
|
||||||
|
@ -184,6 +185,14 @@ class DomainRequestFixture:
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Lumped under .atomic to ensure we don't make redundant DB calls.
|
||||||
|
# This bundles them all together, and then saves it in a single call.
|
||||||
|
with transaction.atomic():
|
||||||
|
cls._create_domain_requests(users)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_domain_requests(cls, users):
|
||||||
|
"""Creates DomainRequests given a list of users"""
|
||||||
for user in users:
|
for user in users:
|
||||||
logger.debug("Loading domain requests for %s" % user)
|
logger.debug("Loading domain requests for %s" % user)
|
||||||
for app in cls.DA:
|
for app in cls.DA:
|
||||||
|
@ -211,8 +220,16 @@ class DomainFixture(DomainRequestFixture):
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Lumped under .atomic to ensure we don't make redundant DB calls.
|
||||||
|
# This bundles them all together, and then saves it in a single call.
|
||||||
|
with transaction.atomic():
|
||||||
|
# approve each user associated with `in review` status domains
|
||||||
|
DomainFixture._approve_domain_requests(users)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _approve_domain_requests(users):
|
||||||
|
"""Approves all provided domain requests if they are in the state in_review"""
|
||||||
for user in users:
|
for user in users:
|
||||||
# approve one of each users in review status domains
|
|
||||||
domain_request = DomainRequest.objects.filter(
|
domain_request = DomainRequest.objects.filter(
|
||||||
creator=user, status=DomainRequest.DomainRequestStatus.IN_REVIEW
|
creator=user, status=DomainRequest.DomainRequestStatus.IN_REVIEW
|
||||||
).last()
|
).last()
|
||||||
|
@ -220,5 +237,13 @@ class DomainFixture(DomainRequestFixture):
|
||||||
|
|
||||||
# We don't want fixtures sending out real emails to
|
# We don't want fixtures sending out real emails to
|
||||||
# fake email addresses, so we just skip that and log it instead
|
# fake email addresses, so we just skip that and log it instead
|
||||||
|
|
||||||
|
# All approvals require an investigator, so if there is none,
|
||||||
|
# assign one.
|
||||||
|
if domain_request.investigator is None:
|
||||||
|
# All "users" in fixtures have admin perms per prior config.
|
||||||
|
# No need to check for that.
|
||||||
|
domain_request.investigator = random.choice(users) # nosec
|
||||||
|
|
||||||
domain_request.approve(send_email=False)
|
domain_request.approve(send_email=False)
|
||||||
domain_request.save()
|
domain_request.save()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from faker import Faker
|
from faker import Faker
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
User,
|
User,
|
||||||
|
@ -186,5 +187,12 @@ class UserFixture:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
cls.load_users(cls, cls.ADMINS, "full_access_group")
|
# Lumped under .atomic to ensure we don't make redundant DB calls.
|
||||||
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")
|
# This bundles them all together, and then saves it in a single call.
|
||||||
|
# This is slightly different then bulk_create or bulk_update, in that
|
||||||
|
# you still get the same behaviour of .save(), but those incremental
|
||||||
|
# steps now do not need to close/reopen a db connection,
|
||||||
|
# instead they share one.
|
||||||
|
with transaction.atomic():
|
||||||
|
cls.load_users(cls, cls.ADMINS, "full_access_group")
|
||||||
|
cls.load_users(cls, cls.STAFF, "cisa_analysts_group")
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-12 16:50
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0072_alter_publiccontact_fax_alter_publiccontact_voice"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="approved_domain",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The approved domain",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="domain_request",
|
||||||
|
to="registrar.domain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="creator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="domain_requests_created",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="investigator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="domain_requests_investigating",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="other_contacts",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="contact_domain_requests", to="registrar.contact", verbose_name="contacts"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="requested_domain",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The requested domain",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="domain_request",
|
||||||
|
to="registrar.draftdomain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="submitter",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="submitted_domain_requests",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="domain_application",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Associated domain request",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="DomainRequest_info",
|
||||||
|
to="registrar.domainapplication",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="other_contacts",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="contact_domain_requests_information",
|
||||||
|
to="registrar.contact",
|
||||||
|
verbose_name="contacts",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="submitter",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="submitted_domain_requests_information",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,685 +0,0 @@
|
||||||
# Generated by Django 4.2.10 on 2024-03-07 21:52
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django_fsm
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("registrar", "0072_alter_publiccontact_fax_alter_publiccontact_voice"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="DomainRequest",
|
|
||||||
fields=[
|
|
||||||
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
|
||||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
||||||
("updated_at", models.DateTimeField(auto_now=True)),
|
|
||||||
(
|
|
||||||
"status",
|
|
||||||
django_fsm.FSMField(
|
|
||||||
choices=[
|
|
||||||
("started", "Started"),
|
|
||||||
("submitted", "Submitted"),
|
|
||||||
("in review", "In review"),
|
|
||||||
("action needed", "Action needed"),
|
|
||||||
("approved", "Approved"),
|
|
||||||
("withdrawn", "Withdrawn"),
|
|
||||||
("rejected", "Rejected"),
|
|
||||||
("ineligible", "Ineligible"),
|
|
||||||
],
|
|
||||||
default="started",
|
|
||||||
max_length=50,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"rejection_reason",
|
|
||||||
models.TextField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
("purpose_not_met", "Purpose requirements not met"),
|
|
||||||
("requestor_not_eligible", "Requestor not eligible to make request"),
|
|
||||||
("org_has_domain", "Org already has a .gov domain"),
|
|
||||||
("contacts_not_verified", "Org contacts couldn't be verified"),
|
|
||||||
("org_not_eligible", "Org not eligible for a .gov domain"),
|
|
||||||
("naming_not_met", "Naming requirements not met"),
|
|
||||||
("other", "Other/Unspecified"),
|
|
||||||
],
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"organization_type",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
("federal", "Federal"),
|
|
||||||
("interstate", "Interstate"),
|
|
||||||
("state_or_territory", "State or territory"),
|
|
||||||
("tribal", "Tribal"),
|
|
||||||
("county", "County"),
|
|
||||||
("city", "City"),
|
|
||||||
("special_district", "Special district"),
|
|
||||||
("school_district", "School district"),
|
|
||||||
],
|
|
||||||
help_text="Type of organization",
|
|
||||||
max_length=255,
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"federally_recognized_tribe",
|
|
||||||
models.BooleanField(help_text="Is the tribe federally recognized", null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"state_recognized_tribe",
|
|
||||||
models.BooleanField(help_text="Is the tribe recognized by a state", null=True),
|
|
||||||
),
|
|
||||||
("tribe_name", models.CharField(blank=True, help_text="Name of tribe", null=True)),
|
|
||||||
(
|
|
||||||
"federal_agency",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
(
|
|
||||||
"Administrative Conference of the United States",
|
|
||||||
"Administrative Conference of the United States",
|
|
||||||
),
|
|
||||||
("Advisory Council on Historic Preservation", "Advisory Council on Historic Preservation"),
|
|
||||||
("American Battle Monuments Commission", "American Battle Monuments Commission"),
|
|
||||||
("AMTRAK", "AMTRAK"),
|
|
||||||
("Appalachian Regional Commission", "Appalachian Regional Commission"),
|
|
||||||
(
|
|
||||||
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
|
||||||
"Appraisal Subcommittee of the Federal Financial Institutions Examination Council",
|
|
||||||
),
|
|
||||||
("Appraisal Subcommittee", "Appraisal Subcommittee"),
|
|
||||||
("Architect of the Capitol", "Architect of the Capitol"),
|
|
||||||
("Armed Forces Retirement Home", "Armed Forces Retirement Home"),
|
|
||||||
(
|
|
||||||
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
|
||||||
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Barry Goldwater Scholarship and Excellence in Education Program",
|
|
||||||
"Barry Goldwater Scholarship and Excellence in Education Program",
|
|
||||||
),
|
|
||||||
("Central Intelligence Agency", "Central Intelligence Agency"),
|
|
||||||
("Chemical Safety Board", "Chemical Safety Board"),
|
|
||||||
(
|
|
||||||
"Christopher Columbus Fellowship Foundation",
|
|
||||||
"Christopher Columbus Fellowship Foundation",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Civil Rights Cold Case Records Review Board",
|
|
||||||
"Civil Rights Cold Case Records Review Board",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Commission for the Preservation of America's Heritage Abroad",
|
|
||||||
"Commission for the Preservation of America's Heritage Abroad",
|
|
||||||
),
|
|
||||||
("Commission of Fine Arts", "Commission of Fine Arts"),
|
|
||||||
(
|
|
||||||
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
|
||||||
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
|
||||||
),
|
|
||||||
("Commodity Futures Trading Commission", "Commodity Futures Trading Commission"),
|
|
||||||
("Congressional Budget Office", "Congressional Budget Office"),
|
|
||||||
("Consumer Financial Protection Bureau", "Consumer Financial Protection Bureau"),
|
|
||||||
("Consumer Product Safety Commission", "Consumer Product Safety Commission"),
|
|
||||||
(
|
|
||||||
"Corporation for National & Community Service",
|
|
||||||
"Corporation for National & Community Service",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Corporation for National and Community Service",
|
|
||||||
"Corporation for National and Community Service",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Council of Inspectors General on Integrity and Efficiency",
|
|
||||||
"Council of Inspectors General on Integrity and Efficiency",
|
|
||||||
),
|
|
||||||
("Court Services and Offender Supervision", "Court Services and Offender Supervision"),
|
|
||||||
("Cyberspace Solarium Commission", "Cyberspace Solarium Commission"),
|
|
||||||
(
|
|
||||||
"DC Court Services and Offender Supervision Agency",
|
|
||||||
"DC Court Services and Offender Supervision Agency",
|
|
||||||
),
|
|
||||||
("DC Pre-trial Services", "DC Pre-trial Services"),
|
|
||||||
("Defense Nuclear Facilities Safety Board", "Defense Nuclear Facilities Safety Board"),
|
|
||||||
("Delta Regional Authority", "Delta Regional Authority"),
|
|
||||||
("Denali Commission", "Denali Commission"),
|
|
||||||
("Department of Agriculture", "Department of Agriculture"),
|
|
||||||
("Department of Commerce", "Department of Commerce"),
|
|
||||||
("Department of Defense", "Department of Defense"),
|
|
||||||
("Department of Education", "Department of Education"),
|
|
||||||
("Department of Energy", "Department of Energy"),
|
|
||||||
("Department of Health and Human Services", "Department of Health and Human Services"),
|
|
||||||
("Department of Homeland Security", "Department of Homeland Security"),
|
|
||||||
(
|
|
||||||
"Department of Housing and Urban Development",
|
|
||||||
"Department of Housing and Urban Development",
|
|
||||||
),
|
|
||||||
("Department of Justice", "Department of Justice"),
|
|
||||||
("Department of Labor", "Department of Labor"),
|
|
||||||
("Department of State", "Department of State"),
|
|
||||||
("Department of the Interior", "Department of the Interior"),
|
|
||||||
("Department of the Treasury", "Department of the Treasury"),
|
|
||||||
("Department of Transportation", "Department of Transportation"),
|
|
||||||
("Department of Veterans Affairs", "Department of Veterans Affairs"),
|
|
||||||
("Director of National Intelligence", "Director of National Intelligence"),
|
|
||||||
("Dwight D. Eisenhower Memorial Commission", "Dwight D. Eisenhower Memorial Commission"),
|
|
||||||
("Election Assistance Commission", "Election Assistance Commission"),
|
|
||||||
("Environmental Protection Agency", "Environmental Protection Agency"),
|
|
||||||
("Equal Employment Opportunity Commission", "Equal Employment Opportunity Commission"),
|
|
||||||
("Executive Office of the President", "Executive Office of the President"),
|
|
||||||
("Export-Import Bank of the United States", "Export-Import Bank of the United States"),
|
|
||||||
("Export/Import Bank of the U.S.", "Export/Import Bank of the U.S."),
|
|
||||||
("Farm Credit Administration", "Farm Credit Administration"),
|
|
||||||
("Farm Credit System Insurance Corporation", "Farm Credit System Insurance Corporation"),
|
|
||||||
("Federal Communications Commission", "Federal Communications Commission"),
|
|
||||||
("Federal Deposit Insurance Corporation", "Federal Deposit Insurance Corporation"),
|
|
||||||
("Federal Election Commission", "Federal Election Commission"),
|
|
||||||
("Federal Energy Regulatory Commission", "Federal Energy Regulatory Commission"),
|
|
||||||
(
|
|
||||||
"Federal Financial Institutions Examination Council",
|
|
||||||
"Federal Financial Institutions Examination Council",
|
|
||||||
),
|
|
||||||
("Federal Housing Finance Agency", "Federal Housing Finance Agency"),
|
|
||||||
("Federal Judiciary", "Federal Judiciary"),
|
|
||||||
("Federal Labor Relations Authority", "Federal Labor Relations Authority"),
|
|
||||||
("Federal Maritime Commission", "Federal Maritime Commission"),
|
|
||||||
(
|
|
||||||
"Federal Mediation and Conciliation Service",
|
|
||||||
"Federal Mediation and Conciliation Service",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Federal Mine Safety and Health Review Commission",
|
|
||||||
"Federal Mine Safety and Health Review Commission",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Federal Permitting Improvement Steering Council",
|
|
||||||
"Federal Permitting Improvement Steering Council",
|
|
||||||
),
|
|
||||||
("Federal Reserve Board of Governors", "Federal Reserve Board of Governors"),
|
|
||||||
("Federal Reserve System", "Federal Reserve System"),
|
|
||||||
("Federal Trade Commission", "Federal Trade Commission"),
|
|
||||||
("General Services Administration", "General Services Administration"),
|
|
||||||
("gov Administration", "gov Administration"),
|
|
||||||
("Government Accountability Office", "Government Accountability Office"),
|
|
||||||
("Government Publishing Office", "Government Publishing Office"),
|
|
||||||
("Gulf Coast Ecosystem Restoration Council", "Gulf Coast Ecosystem Restoration Council"),
|
|
||||||
("Harry S Truman Scholarship Foundation", "Harry S Truman Scholarship Foundation"),
|
|
||||||
("Harry S. Truman Scholarship Foundation", "Harry S. Truman Scholarship Foundation"),
|
|
||||||
("Institute of Museum and Library Services", "Institute of Museum and Library Services"),
|
|
||||||
("Institute of Peace", "Institute of Peace"),
|
|
||||||
("Inter-American Foundation", "Inter-American Foundation"),
|
|
||||||
(
|
|
||||||
"International Boundary and Water Commission: United States and Mexico",
|
|
||||||
"International Boundary and Water Commission: United States and Mexico",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"International Boundary Commission: United States and Canada",
|
|
||||||
"International Boundary Commission: United States and Canada",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"International Joint Commission: United States and Canada",
|
|
||||||
"International Joint Commission: United States and Canada",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"James Madison Memorial Fellowship Foundation",
|
|
||||||
"James Madison Memorial Fellowship Foundation",
|
|
||||||
),
|
|
||||||
("Japan-United States Friendship Commission", "Japan-United States Friendship Commission"),
|
|
||||||
("Japan-US Friendship Commission", "Japan-US Friendship Commission"),
|
|
||||||
(
|
|
||||||
"John F. Kennedy Center for Performing Arts",
|
|
||||||
"John F. Kennedy Center for Performing Arts",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"John F. Kennedy Center for the Performing Arts",
|
|
||||||
"John F. Kennedy Center for the Performing Arts",
|
|
||||||
),
|
|
||||||
("Legal Services Corporation", "Legal Services Corporation"),
|
|
||||||
("Legislative Branch", "Legislative Branch"),
|
|
||||||
("Library of Congress", "Library of Congress"),
|
|
||||||
("Marine Mammal Commission", "Marine Mammal Commission"),
|
|
||||||
(
|
|
||||||
"Medicaid and CHIP Payment and Access Commission",
|
|
||||||
"Medicaid and CHIP Payment and Access Commission",
|
|
||||||
),
|
|
||||||
("Medical Payment Advisory Commission", "Medical Payment Advisory Commission"),
|
|
||||||
("Medicare Payment Advisory Commission", "Medicare Payment Advisory Commission"),
|
|
||||||
("Merit Systems Protection Board", "Merit Systems Protection Board"),
|
|
||||||
("Millennium Challenge Corporation", "Millennium Challenge Corporation"),
|
|
||||||
(
|
|
||||||
"Morris K. Udall and Stewart L. Udall Foundation",
|
|
||||||
"Morris K. Udall and Stewart L. Udall Foundation",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"National Aeronautics and Space Administration",
|
|
||||||
"National Aeronautics and Space Administration",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"National Archives and Records Administration",
|
|
||||||
"National Archives and Records Administration",
|
|
||||||
),
|
|
||||||
("National Capital Planning Commission", "National Capital Planning Commission"),
|
|
||||||
("National Council on Disability", "National Council on Disability"),
|
|
||||||
("National Credit Union Administration", "National Credit Union Administration"),
|
|
||||||
("National Endowment for the Arts", "National Endowment for the Arts"),
|
|
||||||
("National Endowment for the Humanities", "National Endowment for the Humanities"),
|
|
||||||
(
|
|
||||||
"National Foundation on the Arts and the Humanities",
|
|
||||||
"National Foundation on the Arts and the Humanities",
|
|
||||||
),
|
|
||||||
("National Gallery of Art", "National Gallery of Art"),
|
|
||||||
("National Indian Gaming Commission", "National Indian Gaming Commission"),
|
|
||||||
("National Labor Relations Board", "National Labor Relations Board"),
|
|
||||||
("National Mediation Board", "National Mediation Board"),
|
|
||||||
("National Science Foundation", "National Science Foundation"),
|
|
||||||
(
|
|
||||||
"National Security Commission on Artificial Intelligence",
|
|
||||||
"National Security Commission on Artificial Intelligence",
|
|
||||||
),
|
|
||||||
("National Transportation Safety Board", "National Transportation Safety Board"),
|
|
||||||
(
|
|
||||||
"Networking Information Technology Research and Development",
|
|
||||||
"Networking Information Technology Research and Development",
|
|
||||||
),
|
|
||||||
("Non-Federal Agency", "Non-Federal Agency"),
|
|
||||||
("Northern Border Regional Commission", "Northern Border Regional Commission"),
|
|
||||||
("Nuclear Regulatory Commission", "Nuclear Regulatory Commission"),
|
|
||||||
("Nuclear Safety Oversight Committee", "Nuclear Safety Oversight Committee"),
|
|
||||||
("Nuclear Waste Technical Review Board", "Nuclear Waste Technical Review Board"),
|
|
||||||
(
|
|
||||||
"Occupational Safety & Health Review Commission",
|
|
||||||
"Occupational Safety & Health Review Commission",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Occupational Safety and Health Review Commission",
|
|
||||||
"Occupational Safety and Health Review Commission",
|
|
||||||
),
|
|
||||||
("Office of Compliance", "Office of Compliance"),
|
|
||||||
("Office of Congressional Workplace Rights", "Office of Congressional Workplace Rights"),
|
|
||||||
("Office of Government Ethics", "Office of Government Ethics"),
|
|
||||||
(
|
|
||||||
"Office of Navajo and Hopi Indian Relocation",
|
|
||||||
"Office of Navajo and Hopi Indian Relocation",
|
|
||||||
),
|
|
||||||
("Office of Personnel Management", "Office of Personnel Management"),
|
|
||||||
("Open World Leadership Center", "Open World Leadership Center"),
|
|
||||||
("Overseas Private Investment Corporation", "Overseas Private Investment Corporation"),
|
|
||||||
("Peace Corps", "Peace Corps"),
|
|
||||||
("Pension Benefit Guaranty Corporation", "Pension Benefit Guaranty Corporation"),
|
|
||||||
("Postal Regulatory Commission", "Postal Regulatory Commission"),
|
|
||||||
("Presidio Trust", "Presidio Trust"),
|
|
||||||
(
|
|
||||||
"Privacy and Civil Liberties Oversight Board",
|
|
||||||
"Privacy and Civil Liberties Oversight Board",
|
|
||||||
),
|
|
||||||
("Public Buildings Reform Board", "Public Buildings Reform Board"),
|
|
||||||
(
|
|
||||||
"Public Defender Service for the District of Columbia",
|
|
||||||
"Public Defender Service for the District of Columbia",
|
|
||||||
),
|
|
||||||
("Railroad Retirement Board", "Railroad Retirement Board"),
|
|
||||||
("Securities and Exchange Commission", "Securities and Exchange Commission"),
|
|
||||||
("Selective Service System", "Selective Service System"),
|
|
||||||
("Small Business Administration", "Small Business Administration"),
|
|
||||||
("Smithsonian Institution", "Smithsonian Institution"),
|
|
||||||
("Social Security Administration", "Social Security Administration"),
|
|
||||||
("Social Security Advisory Board", "Social Security Advisory Board"),
|
|
||||||
("Southeast Crescent Regional Commission", "Southeast Crescent Regional Commission"),
|
|
||||||
("Southwest Border Regional Commission", "Southwest Border Regional Commission"),
|
|
||||||
("State Justice Institute", "State Justice Institute"),
|
|
||||||
("State, Local, and Tribal Government", "State, Local, and Tribal Government"),
|
|
||||||
("Stennis Center for Public Service", "Stennis Center for Public Service"),
|
|
||||||
("Surface Transportation Board", "Surface Transportation Board"),
|
|
||||||
("Tennessee Valley Authority", "Tennessee Valley Authority"),
|
|
||||||
("The Executive Office of the President", "The Executive Office of the President"),
|
|
||||||
("The Intelligence Community", "The Intelligence Community"),
|
|
||||||
("The Legislative Branch", "The Legislative Branch"),
|
|
||||||
("The Supreme Court", "The Supreme Court"),
|
|
||||||
(
|
|
||||||
"The United States World War One Centennial Commission",
|
|
||||||
"The United States World War One Centennial Commission",
|
|
||||||
),
|
|
||||||
("U.S. Access Board", "U.S. Access Board"),
|
|
||||||
("U.S. Agency for Global Media", "U.S. Agency for Global Media"),
|
|
||||||
("U.S. Agency for International Development", "U.S. Agency for International Development"),
|
|
||||||
("U.S. Capitol Police", "U.S. Capitol Police"),
|
|
||||||
("U.S. Chemical Safety Board", "U.S. Chemical Safety Board"),
|
|
||||||
(
|
|
||||||
"U.S. China Economic and Security Review Commission",
|
|
||||||
"U.S. China Economic and Security Review Commission",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
|
||||||
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
|
||||||
),
|
|
||||||
("U.S. Commission of Fine Arts", "U.S. Commission of Fine Arts"),
|
|
||||||
("U.S. Commission on Civil Rights", "U.S. Commission on Civil Rights"),
|
|
||||||
(
|
|
||||||
"U.S. Commission on International Religious Freedom",
|
|
||||||
"U.S. Commission on International Religious Freedom",
|
|
||||||
),
|
|
||||||
("U.S. Courts", "U.S. Courts"),
|
|
||||||
("U.S. Department of Agriculture", "U.S. Department of Agriculture"),
|
|
||||||
("U.S. Interagency Council on Homelessness", "U.S. Interagency Council on Homelessness"),
|
|
||||||
("U.S. International Trade Commission", "U.S. International Trade Commission"),
|
|
||||||
("U.S. Nuclear Waste Technical Review Board", "U.S. Nuclear Waste Technical Review Board"),
|
|
||||||
("U.S. Office of Special Counsel", "U.S. Office of Special Counsel"),
|
|
||||||
("U.S. Peace Corps", "U.S. Peace Corps"),
|
|
||||||
("U.S. Postal Service", "U.S. Postal Service"),
|
|
||||||
("U.S. Semiquincentennial Commission", "U.S. Semiquincentennial Commission"),
|
|
||||||
("U.S. Trade and Development Agency", "U.S. Trade and Development Agency"),
|
|
||||||
(
|
|
||||||
"U.S.-China Economic and Security Review Commission",
|
|
||||||
"U.S.-China Economic and Security Review Commission",
|
|
||||||
),
|
|
||||||
("Udall Foundation", "Udall Foundation"),
|
|
||||||
("United States AbilityOne", "United States AbilityOne"),
|
|
||||||
("United States Access Board", "United States Access Board"),
|
|
||||||
(
|
|
||||||
"United States African Development Foundation",
|
|
||||||
"United States African Development Foundation",
|
|
||||||
),
|
|
||||||
("United States Agency for Global Media", "United States Agency for Global Media"),
|
|
||||||
("United States Arctic Research Commission", "United States Arctic Research Commission"),
|
|
||||||
(
|
|
||||||
"United States Global Change Research Program",
|
|
||||||
"United States Global Change Research Program",
|
|
||||||
),
|
|
||||||
("United States Holocaust Memorial Museum", "United States Holocaust Memorial Museum"),
|
|
||||||
("United States Institute of Peace", "United States Institute of Peace"),
|
|
||||||
(
|
|
||||||
"United States Interagency Council on Homelessness",
|
|
||||||
"United States Interagency Council on Homelessness",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"United States International Development Finance Corporation",
|
|
||||||
"United States International Development Finance Corporation",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"United States International Trade Commission",
|
|
||||||
"United States International Trade Commission",
|
|
||||||
),
|
|
||||||
("United States Postal Service", "United States Postal Service"),
|
|
||||||
("United States Senate", "United States Senate"),
|
|
||||||
(
|
|
||||||
"United States Trade and Development Agency",
|
|
||||||
"United States Trade and Development Agency",
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"Utah Reclamation Mitigation and Conservation Commission",
|
|
||||||
"Utah Reclamation Mitigation and Conservation Commission",
|
|
||||||
),
|
|
||||||
("Vietnam Education Foundation", "Vietnam Education Foundation"),
|
|
||||||
("Western Hemisphere Drug Policy Commission", "Western Hemisphere Drug Policy Commission"),
|
|
||||||
(
|
|
||||||
"Woodrow Wilson International Center for Scholars",
|
|
||||||
"Woodrow Wilson International Center for Scholars",
|
|
||||||
),
|
|
||||||
("World War I Centennial Commission", "World War I Centennial Commission"),
|
|
||||||
],
|
|
||||||
help_text="Federal agency",
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"federal_type",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[("executive", "Executive"), ("judicial", "Judicial"), ("legislative", "Legislative")],
|
|
||||||
help_text="Federal government branch",
|
|
||||||
max_length=50,
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_election_board",
|
|
||||||
models.BooleanField(blank=True, help_text="Is your organization an election office?", null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"organization_name",
|
|
||||||
models.CharField(blank=True, db_index=True, help_text="Organization name", null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"address_line1",
|
|
||||||
models.CharField(blank=True, help_text="Street address", null=True, verbose_name="Address line 1"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"address_line2",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Street address line 2 (optional)",
|
|
||||||
null=True,
|
|
||||||
verbose_name="Address line 2",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("city", models.CharField(blank=True, help_text="City", null=True)),
|
|
||||||
(
|
|
||||||
"state_territory",
|
|
||||||
models.CharField(
|
|
||||||
blank=True,
|
|
||||||
choices=[
|
|
||||||
("AL", "Alabama (AL)"),
|
|
||||||
("AK", "Alaska (AK)"),
|
|
||||||
("AS", "American Samoa (AS)"),
|
|
||||||
("AZ", "Arizona (AZ)"),
|
|
||||||
("AR", "Arkansas (AR)"),
|
|
||||||
("CA", "California (CA)"),
|
|
||||||
("CO", "Colorado (CO)"),
|
|
||||||
("CT", "Connecticut (CT)"),
|
|
||||||
("DE", "Delaware (DE)"),
|
|
||||||
("DC", "District of Columbia (DC)"),
|
|
||||||
("FL", "Florida (FL)"),
|
|
||||||
("GA", "Georgia (GA)"),
|
|
||||||
("GU", "Guam (GU)"),
|
|
||||||
("HI", "Hawaii (HI)"),
|
|
||||||
("ID", "Idaho (ID)"),
|
|
||||||
("IL", "Illinois (IL)"),
|
|
||||||
("IN", "Indiana (IN)"),
|
|
||||||
("IA", "Iowa (IA)"),
|
|
||||||
("KS", "Kansas (KS)"),
|
|
||||||
("KY", "Kentucky (KY)"),
|
|
||||||
("LA", "Louisiana (LA)"),
|
|
||||||
("ME", "Maine (ME)"),
|
|
||||||
("MD", "Maryland (MD)"),
|
|
||||||
("MA", "Massachusetts (MA)"),
|
|
||||||
("MI", "Michigan (MI)"),
|
|
||||||
("MN", "Minnesota (MN)"),
|
|
||||||
("MS", "Mississippi (MS)"),
|
|
||||||
("MO", "Missouri (MO)"),
|
|
||||||
("MT", "Montana (MT)"),
|
|
||||||
("NE", "Nebraska (NE)"),
|
|
||||||
("NV", "Nevada (NV)"),
|
|
||||||
("NH", "New Hampshire (NH)"),
|
|
||||||
("NJ", "New Jersey (NJ)"),
|
|
||||||
("NM", "New Mexico (NM)"),
|
|
||||||
("NY", "New York (NY)"),
|
|
||||||
("NC", "North Carolina (NC)"),
|
|
||||||
("ND", "North Dakota (ND)"),
|
|
||||||
("MP", "Northern Mariana Islands (MP)"),
|
|
||||||
("OH", "Ohio (OH)"),
|
|
||||||
("OK", "Oklahoma (OK)"),
|
|
||||||
("OR", "Oregon (OR)"),
|
|
||||||
("PA", "Pennsylvania (PA)"),
|
|
||||||
("PR", "Puerto Rico (PR)"),
|
|
||||||
("RI", "Rhode Island (RI)"),
|
|
||||||
("SC", "South Carolina (SC)"),
|
|
||||||
("SD", "South Dakota (SD)"),
|
|
||||||
("TN", "Tennessee (TN)"),
|
|
||||||
("TX", "Texas (TX)"),
|
|
||||||
("UM", "United States Minor Outlying Islands (UM)"),
|
|
||||||
("UT", "Utah (UT)"),
|
|
||||||
("VT", "Vermont (VT)"),
|
|
||||||
("VI", "Virgin Islands (VI)"),
|
|
||||||
("VA", "Virginia (VA)"),
|
|
||||||
("WA", "Washington (WA)"),
|
|
||||||
("WV", "West Virginia (WV)"),
|
|
||||||
("WI", "Wisconsin (WI)"),
|
|
||||||
("WY", "Wyoming (WY)"),
|
|
||||||
("AA", "Armed Forces Americas (AA)"),
|
|
||||||
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
|
|
||||||
("AP", "Armed Forces Pacific (AP)"),
|
|
||||||
],
|
|
||||||
help_text="State, territory, or military post",
|
|
||||||
max_length=2,
|
|
||||||
null=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"zipcode",
|
|
||||||
models.CharField(blank=True, db_index=True, help_text="Zip code", max_length=10, null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"urbanization",
|
|
||||||
models.CharField(blank=True, help_text="Urbanization (required for Puerto Rico only)", null=True),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"about_your_organization",
|
|
||||||
models.TextField(blank=True, help_text="Information about your organization", null=True),
|
|
||||||
),
|
|
||||||
("purpose", models.TextField(blank=True, help_text="Purpose of your domain", null=True)),
|
|
||||||
(
|
|
||||||
"no_other_contacts_rationale",
|
|
||||||
models.TextField(blank=True, help_text="Reason for listing no additional contacts", null=True),
|
|
||||||
),
|
|
||||||
("anything_else", models.TextField(blank=True, help_text="Anything else?", null=True)),
|
|
||||||
(
|
|
||||||
"is_policy_acknowledged",
|
|
||||||
models.BooleanField(blank=True, help_text="Acknowledged .gov acceptable use policy", null=True),
|
|
||||||
),
|
|
||||||
("submission_date", models.DateField(blank=True, default=None, help_text="Date submitted", null=True)),
|
|
||||||
("notes", models.TextField(blank=True, help_text="Notes about this request", null=True)),
|
|
||||||
(
|
|
||||||
"alternative_domains",
|
|
||||||
models.ManyToManyField(blank=True, related_name="alternatives+", to="registrar.website"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"approved_domain",
|
|
||||||
models.OneToOneField(
|
|
||||||
blank=True,
|
|
||||||
help_text="The approved domain",
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="domain_request",
|
|
||||||
to="registrar.domain",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"authorizing_official",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="authorizing_official",
|
|
||||||
to="registrar.contact",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"creator",
|
|
||||||
models.ForeignKey(
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="domain_requests_created",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"current_websites",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True, related_name="current+", to="registrar.website", verbose_name="websites"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"investigator",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.SET_NULL,
|
|
||||||
related_name="domain_requests_investigating",
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"other_contacts",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="contact_domain_requests",
|
|
||||||
to="registrar.contact",
|
|
||||||
verbose_name="contacts",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"requested_domain",
|
|
||||||
models.OneToOneField(
|
|
||||||
blank=True,
|
|
||||||
help_text="The requested domain",
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="domain_request",
|
|
||||||
to="registrar.draftdomain",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"submitter",
|
|
||||||
models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="submitted_domain_requests",
|
|
||||||
to="registrar.contact",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name="domaininformation",
|
|
||||||
name="domain_application",
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="domaininformation",
|
|
||||||
name="other_contacts",
|
|
||||||
field=models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
related_name="contact_domain_requests_information",
|
|
||||||
to="registrar.contact",
|
|
||||||
verbose_name="contacts",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="domaininformation",
|
|
||||||
name="submitter",
|
|
||||||
field=models.ForeignKey(
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="submitted_domain_requests_information",
|
|
||||||
to="registrar.contact",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.DeleteModel(
|
|
||||||
name="DomainApplication",
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name="domaininformation",
|
|
||||||
name="domain_request",
|
|
||||||
field=models.OneToOneField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Associated domain request",
|
|
||||||
null=True,
|
|
||||||
on_delete=django.db.models.deletion.PROTECT,
|
|
||||||
related_name="DomainRequest_info",
|
|
||||||
to="registrar.domainrequest",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-12 16:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0073_alter_domainapplication_approved_domain_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name="DomainApplication",
|
||||||
|
new_name="DomainRequest",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
old_name="domain_application",
|
||||||
|
new_name="domain_request",
|
||||||
|
),
|
||||||
|
]
|
|
@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any:
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("registrar", "0073_domainrequest_and_more"),
|
("registrar", "0074_rename_domainapplication_domainrequest_and_more"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -9,6 +9,7 @@ from django.db import models
|
||||||
from django_fsm import FSMField, transition # type: ignore
|
from django_fsm import FSMField, transition # type: ignore
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from registrar.models.domain import Domain
|
from registrar.models.domain import Domain
|
||||||
|
from registrar.utility.errors import FSMApplicationError, FSMErrorCodes
|
||||||
|
|
||||||
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
|
||||||
|
@ -645,6 +646,14 @@ class DomainRequest(TimeStampedModel):
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warning("Failed to send confirmation email", exc_info=True)
|
logger.warning("Failed to send confirmation email", exc_info=True)
|
||||||
|
|
||||||
|
def investigator_exists_and_is_staff(self):
|
||||||
|
"""Checks if the current investigator is in a valid state for a state transition"""
|
||||||
|
is_valid = True
|
||||||
|
# Check if an investigator is assigned. No approval is possible without one.
|
||||||
|
if self.investigator is None or not self.investigator.is_staff:
|
||||||
|
is_valid = False
|
||||||
|
return is_valid
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
|
@ -656,7 +665,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
target=DomainRequestStatus.SUBMITTED,
|
target=DomainRequestStatus.SUBMITTED,
|
||||||
)
|
)
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Submit a domain request that is started.
|
"""Submit an domain request that is started.
|
||||||
|
|
||||||
As a side effect, an email notification is sent."""
|
As a side effect, an email notification is sent."""
|
||||||
|
|
||||||
|
@ -664,10 +673,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
# can raise more informative exceptions
|
# can raise more informative exceptions
|
||||||
|
|
||||||
# requested_domain could be None here
|
# requested_domain could be None here
|
||||||
if not hasattr(self, "requested_domain"):
|
if not hasattr(self, "requested_domain") or self.requested_domain is None:
|
||||||
raise ValueError("Requested domain is missing.")
|
|
||||||
|
|
||||||
if self.requested_domain is None:
|
|
||||||
raise ValueError("Requested domain is missing.")
|
raise ValueError("Requested domain is missing.")
|
||||||
|
|
||||||
DraftDomain = apps.get_model("registrar.DraftDomain")
|
DraftDomain = apps.get_model("registrar.DraftDomain")
|
||||||
|
@ -704,10 +710,10 @@ class DomainRequest(TimeStampedModel):
|
||||||
DomainRequestStatus.INELIGIBLE,
|
DomainRequestStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=DomainRequestStatus.IN_REVIEW,
|
target=DomainRequestStatus.IN_REVIEW,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active, investigator_exists_and_is_staff],
|
||||||
)
|
)
|
||||||
def in_review(self):
|
def in_review(self):
|
||||||
"""Investigate a domain request that has been submitted.
|
"""Investigate an domain request that has been submitted.
|
||||||
|
|
||||||
This action is logged.
|
This action is logged.
|
||||||
|
|
||||||
|
@ -736,10 +742,10 @@ class DomainRequest(TimeStampedModel):
|
||||||
DomainRequestStatus.INELIGIBLE,
|
DomainRequestStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=DomainRequestStatus.ACTION_NEEDED,
|
target=DomainRequestStatus.ACTION_NEEDED,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active, investigator_exists_and_is_staff],
|
||||||
)
|
)
|
||||||
def action_needed(self):
|
def action_needed(self):
|
||||||
"""Send back a domain request that is under investigation or rejected.
|
"""Send back an domain request that is under investigation or rejected.
|
||||||
|
|
||||||
This action is logged.
|
This action is logged.
|
||||||
|
|
||||||
|
@ -768,9 +774,10 @@ class DomainRequest(TimeStampedModel):
|
||||||
DomainRequestStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
],
|
],
|
||||||
target=DomainRequestStatus.APPROVED,
|
target=DomainRequestStatus.APPROVED,
|
||||||
|
conditions=[investigator_exists_and_is_staff],
|
||||||
)
|
)
|
||||||
def approve(self, send_email=True):
|
def approve(self, send_email=True):
|
||||||
"""Approve a domain request that has been submitted.
|
"""Approve an domain request that has been submitted.
|
||||||
|
|
||||||
This action cleans up the rejection status if moving away from rejected.
|
This action cleans up the rejection status if moving away from rejected.
|
||||||
|
|
||||||
|
@ -781,8 +788,12 @@ class DomainRequest(TimeStampedModel):
|
||||||
|
|
||||||
# create the domain
|
# create the domain
|
||||||
Domain = apps.get_model("registrar.Domain")
|
Domain = apps.get_model("registrar.Domain")
|
||||||
|
|
||||||
|
# == Check that the domain_request is valid == #
|
||||||
if Domain.objects.filter(name=self.requested_domain.name).exists():
|
if Domain.objects.filter(name=self.requested_domain.name).exists():
|
||||||
raise ValueError("Cannot approve. Requested domain is already in use.")
|
raise FSMApplicationError(code=FSMErrorCodes.APPROVE_DOMAIN_IN_USE)
|
||||||
|
|
||||||
|
# == Create the domain and related components == #
|
||||||
created_domain = Domain.objects.create(name=self.requested_domain.name)
|
created_domain = Domain.objects.create(name=self.requested_domain.name)
|
||||||
self.approved_domain = created_domain
|
self.approved_domain = created_domain
|
||||||
|
|
||||||
|
@ -799,6 +810,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
if self.status == self.DomainRequestStatus.REJECTED:
|
if self.status == self.DomainRequestStatus.REJECTED:
|
||||||
self.rejection_reason = None
|
self.rejection_reason = None
|
||||||
|
|
||||||
|
# == Send out an email == #
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"domain request approved",
|
"domain request approved",
|
||||||
"emails/status_change_approved.txt",
|
"emails/status_change_approved.txt",
|
||||||
|
@ -812,7 +824,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
target=DomainRequestStatus.WITHDRAWN,
|
target=DomainRequestStatus.WITHDRAWN,
|
||||||
)
|
)
|
||||||
def withdraw(self):
|
def withdraw(self):
|
||||||
"""Withdraw a domain request that has been submitted."""
|
"""Withdraw an domain request that has been submitted."""
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"withdraw",
|
"withdraw",
|
||||||
|
@ -824,10 +836,10 @@ class DomainRequest(TimeStampedModel):
|
||||||
field="status",
|
field="status",
|
||||||
source=[DomainRequestStatus.IN_REVIEW, DomainRequestStatus.ACTION_NEEDED, DomainRequestStatus.APPROVED],
|
source=[DomainRequestStatus.IN_REVIEW, DomainRequestStatus.ACTION_NEEDED, DomainRequestStatus.APPROVED],
|
||||||
target=DomainRequestStatus.REJECTED,
|
target=DomainRequestStatus.REJECTED,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active, investigator_exists_and_is_staff],
|
||||||
)
|
)
|
||||||
def reject(self):
|
def reject(self):
|
||||||
"""Reject a domain request that has been submitted.
|
"""Reject an domain request that has been submitted.
|
||||||
|
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade), and send an email notification."""
|
(will cascade), and send an email notification."""
|
||||||
|
@ -850,7 +862,7 @@ class DomainRequest(TimeStampedModel):
|
||||||
DomainRequestStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
],
|
],
|
||||||
target=DomainRequestStatus.INELIGIBLE,
|
target=DomainRequestStatus.INELIGIBLE,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active, investigator_exists_and_is_staff],
|
||||||
)
|
)
|
||||||
def reject_with_prejudice(self):
|
def reject_with_prejudice(self):
|
||||||
"""The applicant is a bad actor, reject with prejudice.
|
"""The applicant is a bad actor, reject with prejudice.
|
||||||
|
|
|
@ -19,15 +19,16 @@
|
||||||
|
|
||||||
<h2>Next steps in this process</h2>
|
<h2>Next steps in this process</h2>
|
||||||
|
|
||||||
<p> We’ll review your request. This usually takes 20 business days. During
|
<p> We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience.</p>
|
||||||
this review we’ll verify that:</p>
|
|
||||||
|
<p>During our review we’ll verify that:</p>
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>Your organization is eligible for a .gov domain.</li>
|
<li>Your organization is eligible for a .gov domain.</li>
|
||||||
<li>You work at the organization and/or can make requests on its behalf.</li>
|
<li>You work at the organization and/or can make requests on its behalf.</li>
|
||||||
<li>Your requested domain meets our naming requirements.</li>
|
<li>Your requested domain meets our naming requirements.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p> We’ll email you if we have questions and when we complete our review. You can <a href="{% url 'home' %}">check the status</a>
|
<p> We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can <a href="{% url 'home' %}">check the status</a>
|
||||||
of your request at any time on the registrar homepage.</p>
|
of your request at any time on the registrar homepage.</p>
|
||||||
|
|
||||||
<p> <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us if you need help during this process</a>.</p>
|
<p> <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us if you need help during this process</a>.</p>
|
||||||
|
|
|
@ -10,12 +10,14 @@ STATUS: Submitted
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
NEXT STEPS
|
NEXT STEPS
|
||||||
We’ll review your request. This usually takes 20 business days. During this review we’ll verify that:
|
We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience.
|
||||||
|
|
||||||
|
During our review we’ll verify that:
|
||||||
- Your organization is eligible for a .gov domain
|
- Your organization is eligible for a .gov domain
|
||||||
- You work at the organization and/or can make requests on its behalf
|
- You work at the organization and/or can make requests on its behalf
|
||||||
- Your requested domain meets our naming requirements
|
- Your requested domain meets our naming requirements
|
||||||
|
|
||||||
We’ll email you if we have questions and when we complete our review. You can check the status of your request at any time on the registrar homepage. <https://manage.get.gov>
|
We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar homepage. <https://manage.get.gov>
|
||||||
|
|
||||||
|
|
||||||
NEED TO MAKE CHANGES?
|
NEED TO MAKE CHANGES?
|
||||||
|
|
|
@ -529,6 +529,7 @@ def completed_domain_request(
|
||||||
user=False,
|
user=False,
|
||||||
submitter=False,
|
submitter=False,
|
||||||
name="city.gov",
|
name="city.gov",
|
||||||
|
investigator=None,
|
||||||
):
|
):
|
||||||
"""A completed domain request."""
|
"""A completed domain request."""
|
||||||
if not user:
|
if not user:
|
||||||
|
@ -558,6 +559,13 @@ def completed_domain_request(
|
||||||
email="testy2@town.com",
|
email="testy2@town.com",
|
||||||
phone="(555) 555 5557",
|
phone="(555) 555 5557",
|
||||||
)
|
)
|
||||||
|
if not investigator:
|
||||||
|
investigator, _ = User.objects.get_or_create(
|
||||||
|
username="incrediblyfakeinvestigator",
|
||||||
|
first_name="Joe",
|
||||||
|
last_name="Bob",
|
||||||
|
is_staff=True,
|
||||||
|
)
|
||||||
domain_request_kwargs = dict(
|
domain_request_kwargs = dict(
|
||||||
organization_type="federal",
|
organization_type="federal",
|
||||||
federal_type="executive",
|
federal_type="executive",
|
||||||
|
@ -573,6 +581,7 @@ def completed_domain_request(
|
||||||
submitter=submitter,
|
submitter=submitter,
|
||||||
creator=user,
|
creator=user,
|
||||||
status=status,
|
status=status,
|
||||||
|
investigator=investigator,
|
||||||
)
|
)
|
||||||
if has_about_your_organization:
|
if has_about_your_organization:
|
||||||
domain_request_kwargs["about_your_organization"] = "e-Government"
|
domain_request_kwargs["about_your_organization"] = "e-Government"
|
||||||
|
@ -591,6 +600,13 @@ def completed_domain_request(
|
||||||
return domain_request
|
return domain_request
|
||||||
|
|
||||||
|
|
||||||
|
def set_domain_request_investigators(domain_request_list: list[DomainRequest], investigator_user: User):
|
||||||
|
"""Helper method that sets the investigator field of all provided domain requests"""
|
||||||
|
for request in domain_request_list:
|
||||||
|
request.investigator = investigator_user
|
||||||
|
request.save()
|
||||||
|
|
||||||
|
|
||||||
def multiple_unalphabetical_domain_objects(
|
def multiple_unalphabetical_domain_objects(
|
||||||
domain_type=AuditedAdminMockData.DOMAIN_REQUEST,
|
domain_type=AuditedAdminMockData.DOMAIN_REQUEST,
|
||||||
):
|
):
|
||||||
|
|
|
@ -17,7 +17,7 @@ from registrar.models import (
|
||||||
import boto3_mocking
|
import boto3_mocking
|
||||||
from registrar.models.transition_domain import TransitionDomain
|
from registrar.models.transition_domain import TransitionDomain
|
||||||
from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore
|
from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore
|
||||||
from .common import MockSESClient, less_console_noise, completed_domain_request
|
from .common import MockSESClient, less_console_noise, completed_domain_request, set_domain_request_investigators
|
||||||
from django_fsm import TransitionNotAllowed
|
from django_fsm import TransitionNotAllowed
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,18 @@ class TestDomainRequest(TestCase):
|
||||||
status=DomainRequest.DomainRequestStatus.INELIGIBLE, name="ineligible.gov"
|
status=DomainRequest.DomainRequestStatus.INELIGIBLE, name="ineligible.gov"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Store all domain request statuses in a variable for ease of use
|
||||||
|
self.all_domain_requests = [
|
||||||
|
self.started_domain_request,
|
||||||
|
self.submitted_domain_request,
|
||||||
|
self.in_review_domain_request,
|
||||||
|
self.action_needed_domain_request,
|
||||||
|
self.approved_domain_request,
|
||||||
|
self.withdrawn_domain_request,
|
||||||
|
self.rejected_domain_request,
|
||||||
|
self.ineligible_domain_request,
|
||||||
|
]
|
||||||
|
|
||||||
self.mock_client = MockSESClient()
|
self.mock_client = MockSESClient()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -219,6 +231,65 @@ class TestDomainRequest(TestCase):
|
||||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
|
||||||
self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0)
|
self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0)
|
||||||
|
|
||||||
|
def assert_fsm_transition_raises_error(self, test_cases, method_to_run):
|
||||||
|
"""Given a list of test cases, check if each transition throws the intended error"""
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise():
|
||||||
|
for domain_request, exception_type in test_cases:
|
||||||
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
|
with self.assertRaises(exception_type):
|
||||||
|
# Retrieve the method by name from the domain_request object and call it
|
||||||
|
method = getattr(domain_request, method_to_run)
|
||||||
|
# Call the method
|
||||||
|
method()
|
||||||
|
|
||||||
|
def assert_fsm_transition_does_not_raise_error(self, test_cases, method_to_run):
|
||||||
|
"""Given a list of test cases, ensure that none of them throw transition errors"""
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client), less_console_noise():
|
||||||
|
for domain_request, exception_type in test_cases:
|
||||||
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
|
try:
|
||||||
|
# Retrieve the method by name from the DomainRequest object and call it
|
||||||
|
method = getattr(domain_request, method_to_run)
|
||||||
|
# Call the method
|
||||||
|
method()
|
||||||
|
except exception_type:
|
||||||
|
self.fail(f"{exception_type} was raised, but it was not expected.")
|
||||||
|
|
||||||
|
def test_submit_transition_allowed_with_no_investigator(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition without an investigator.
|
||||||
|
For submit, this should be valid in all cases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "submit")
|
||||||
|
|
||||||
|
def test_submit_transition_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition with an investigator user that is not staff.
|
||||||
|
For submit, this should be valid in all cases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "submit")
|
||||||
|
|
||||||
def test_submit_transition_allowed(self):
|
def test_submit_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling submit from allowable statuses does raises TransitionNotAllowed.
|
Test that calling submit from allowable statuses does raises TransitionNotAllowed.
|
||||||
|
@ -230,14 +301,27 @@ class TestDomainRequest(TestCase):
|
||||||
(self.withdrawn_domain_request, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "submit")
|
||||||
|
|
||||||
|
def test_submit_transition_allowed_twice(self):
|
||||||
|
"""
|
||||||
|
Test that rotating between submit and in_review doesn't throw an error
|
||||||
|
"""
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for domain_request, exception_type in test_cases:
|
try:
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
# Make a submission
|
||||||
try:
|
self.in_review_domain_request.submit()
|
||||||
domain_request.submit()
|
|
||||||
except TransitionNotAllowed:
|
# Rerun the old method to get back to the original state
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.in_review_domain_request.in_review()
|
||||||
|
|
||||||
|
# Make another submission
|
||||||
|
self.in_review_domain_request.submit()
|
||||||
|
except TransitionNotAllowed:
|
||||||
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
self.assertEqual(self.in_review_domain_request.status, DomainRequest.DomainRequestStatus.SUBMITTED)
|
||||||
|
|
||||||
def test_submit_transition_not_allowed(self):
|
def test_submit_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -250,12 +334,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_raises_error(test_cases, "submit")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.submit()
|
|
||||||
|
|
||||||
def test_in_review_transition_allowed(self):
|
def test_in_review_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -269,14 +348,43 @@ class TestDomainRequest(TestCase):
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "in_review")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
def test_in_review_transition_not_allowed_with_no_investigator(self):
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
"""
|
||||||
try:
|
Tests for attempting to transition without an investigator
|
||||||
domain_request.in_review()
|
"""
|
||||||
except TransitionNotAllowed:
|
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
test_cases = [
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "in_review")
|
||||||
|
|
||||||
|
def test_in_review_transition_not_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition with an investigator that is not staff.
|
||||||
|
This should throw an exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "in_review")
|
||||||
|
|
||||||
def test_in_review_transition_not_allowed(self):
|
def test_in_review_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -288,12 +396,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.withdrawn_domain_request, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_raises_error(test_cases, "in_review")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.in_review()
|
|
||||||
|
|
||||||
def test_action_needed_transition_allowed(self):
|
def test_action_needed_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -305,13 +408,43 @@ class TestDomainRequest(TestCase):
|
||||||
(self.rejected_domain_request, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "action_needed")
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
try:
|
def test_action_needed_transition_not_allowed_with_no_investigator(self):
|
||||||
domain_request.action_needed()
|
"""
|
||||||
except TransitionNotAllowed:
|
Tests for attempting to transition without an investigator
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "action_needed")
|
||||||
|
|
||||||
|
def test_action_needed_transition_not_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition with an investigator that is not staff
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "action_needed")
|
||||||
|
|
||||||
def test_action_needed_transition_not_allowed(self):
|
def test_action_needed_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -323,11 +456,8 @@ class TestDomainRequest(TestCase):
|
||||||
(self.action_needed_domain_request, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_domain_request, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
self.assert_fsm_transition_raises_error(test_cases, "action_needed")
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.action_needed()
|
|
||||||
|
|
||||||
def test_approved_transition_allowed(self):
|
def test_approved_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -340,14 +470,40 @@ class TestDomainRequest(TestCase):
|
||||||
(self.rejected_domain_request, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "approve")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
def test_approved_transition_not_allowed_with_no_investigator(self):
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
"""
|
||||||
try:
|
Tests for attempting to transition without an investigator
|
||||||
domain_request.approve()
|
"""
|
||||||
except TransitionNotAllowed:
|
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "approve")
|
||||||
|
|
||||||
|
def test_approved_transition_not_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition with an investigator that is not staff
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "approve")
|
||||||
|
|
||||||
def test_approved_skips_sending_email(self):
|
def test_approved_skips_sending_email(self):
|
||||||
"""
|
"""
|
||||||
|
@ -372,13 +528,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.withdrawn_domain_request, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "approve")
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.approve()
|
|
||||||
|
|
||||||
def test_withdraw_transition_allowed(self):
|
def test_withdraw_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -390,14 +540,42 @@ class TestDomainRequest(TestCase):
|
||||||
(self.action_needed_domain_request, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
def test_withdraw_transition_allowed_with_no_investigator(self):
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
"""
|
||||||
try:
|
Tests for attempting to transition without an investigator.
|
||||||
domain_request.withdraw()
|
For withdraw, this should be valid in all cases.
|
||||||
except TransitionNotAllowed:
|
"""
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
|
||||||
|
test_cases = [
|
||||||
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw")
|
||||||
|
|
||||||
|
def test_withdraw_transition_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition when investigator is not staff.
|
||||||
|
For withdraw, this should be valid in all cases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "withdraw")
|
||||||
|
|
||||||
def test_withdraw_transition_not_allowed(self):
|
def test_withdraw_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -411,12 +589,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_raises_error(test_cases, "withdraw")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.withdraw()
|
|
||||||
|
|
||||||
def test_reject_transition_allowed(self):
|
def test_reject_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -428,14 +601,40 @@ class TestDomainRequest(TestCase):
|
||||||
(self.approved_domain_request, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "reject")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
def test_reject_transition_not_allowed_with_no_investigator(self):
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
"""
|
||||||
try:
|
Tests for attempting to transition without an investigator
|
||||||
domain_request.reject()
|
"""
|
||||||
except TransitionNotAllowed:
|
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "reject")
|
||||||
|
|
||||||
|
def test_reject_transition_not_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition when investigator is not staff
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "reject")
|
||||||
|
|
||||||
def test_reject_transition_not_allowed(self):
|
def test_reject_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -449,12 +648,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_raises_error(test_cases, "reject")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.reject()
|
|
||||||
|
|
||||||
def test_reject_with_prejudice_transition_allowed(self):
|
def test_reject_with_prejudice_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -467,14 +661,42 @@ class TestDomainRequest(TestCase):
|
||||||
(self.rejected_domain_request, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_does_not_raise_error(test_cases, "reject_with_prejudice")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
def test_reject_with_prejudice_transition_not_allowed_with_no_investigator(self):
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
"""
|
||||||
try:
|
Tests for attempting to transition without an investigator
|
||||||
domain_request.reject_with_prejudice()
|
"""
|
||||||
except TransitionNotAllowed:
|
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to none
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, None)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice")
|
||||||
|
|
||||||
|
def test_reject_with_prejudice_not_allowed_with_investigator_not_staff(self):
|
||||||
|
"""
|
||||||
|
Tests for attempting to transition when investigator is not staff
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_cases = [
|
||||||
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Set all investigators to a user with no staff privs
|
||||||
|
user, _ = User.objects.get_or_create(username="pancakesyrup", is_staff=False)
|
||||||
|
set_domain_request_investigators(self.all_domain_requests, user)
|
||||||
|
|
||||||
|
self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice")
|
||||||
|
|
||||||
def test_reject_with_prejudice_transition_not_allowed(self):
|
def test_reject_with_prejudice_transition_not_allowed(self):
|
||||||
"""
|
"""
|
||||||
|
@ -487,12 +709,7 @@ class TestDomainRequest(TestCase):
|
||||||
(self.ineligible_domain_request, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
self.assert_fsm_transition_raises_error(test_cases, "reject_with_prejudice")
|
||||||
with less_console_noise():
|
|
||||||
for domain_request, exception_type in test_cases:
|
|
||||||
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
|
||||||
with self.assertRaises(exception_type):
|
|
||||||
domain_request.reject_with_prejudice()
|
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_in_review_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_in_review_when_domain_is_active(self):
|
||||||
"""Create a domain request with status approved, create a matching domain that
|
"""Create a domain request with status approved, create a matching domain that
|
||||||
|
@ -666,7 +883,10 @@ class TestPermissions(TestCase):
|
||||||
def test_approval_creates_role(self):
|
def test_approval_creates_role(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain)
|
investigator, _ = User.objects.get_or_create(username="frenchtoast", is_staff=True)
|
||||||
|
domain_request = DomainRequest.objects.create(
|
||||||
|
creator=user, requested_domain=draft_domain, investigator=investigator
|
||||||
|
)
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
@ -697,10 +917,12 @@ class TestDomainInformation(TestCase):
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_approval_creates_info(self):
|
def test_approval_creates_info(self):
|
||||||
self.maxDiff = None
|
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain, notes="test notes")
|
investigator, _ = User.objects.get_or_create(username="frenchtoast", is_staff=True)
|
||||||
|
domain_request = DomainRequest.objects.create(
|
||||||
|
creator=user, requested_domain=draft_domain, notes="test notes", investigator=investigator
|
||||||
|
)
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
|
|
@ -324,7 +324,10 @@ class TestDomainCreation(MockEppLib):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain)
|
investigator, _ = User.objects.get_or_create(username="frenchtoast", is_staff=True)
|
||||||
|
domain_request = DomainRequest.objects.create(
|
||||||
|
creator=user, requested_domain=draft_domain, investigator=investigator
|
||||||
|
)
|
||||||
|
|
||||||
mock_client = MockSESClient()
|
mock_client = MockSESClient()
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
|
|
@ -71,6 +71,48 @@ class GenericError(Exception):
|
||||||
return self._error_mapping.get(code)
|
return self._error_mapping.get(code)
|
||||||
|
|
||||||
|
|
||||||
|
class FSMErrorCodes(IntEnum):
|
||||||
|
"""Used when doing FSM transitions.
|
||||||
|
Overview of generic error codes:
|
||||||
|
- 1 APPROVE_DOMAIN_IN_USE The domain is already in use
|
||||||
|
- 2 NO_INVESTIGATOR No investigator is assigned
|
||||||
|
- 3 INVESTIGATOR_NOT_STAFF Investigator is a non-staff user
|
||||||
|
- 4 INVESTIGATOR_NOT_SUBMITTER The form submitter is not the investigator
|
||||||
|
"""
|
||||||
|
|
||||||
|
APPROVE_DOMAIN_IN_USE = 1
|
||||||
|
NO_INVESTIGATOR = 2
|
||||||
|
INVESTIGATOR_NOT_STAFF = 3
|
||||||
|
INVESTIGATOR_NOT_SUBMITTER = 4
|
||||||
|
|
||||||
|
|
||||||
|
class FSMApplicationError(Exception):
|
||||||
|
"""
|
||||||
|
Used to raise exceptions when doing FSM Transitions.
|
||||||
|
Uses `FSMErrorCodes` as an enum.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_error_mapping = {
|
||||||
|
FSMErrorCodes.APPROVE_DOMAIN_IN_USE: ("Cannot approve. Requested domain is already in use."),
|
||||||
|
FSMErrorCodes.NO_INVESTIGATOR: ("Investigator is required for this status."),
|
||||||
|
FSMErrorCodes.INVESTIGATOR_NOT_STAFF: ("Investigator is not a staff user."),
|
||||||
|
FSMErrorCodes.INVESTIGATOR_NOT_SUBMITTER: ("Only the assigned investigator can make this change."),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, code=None, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.code = code
|
||||||
|
if self.code in self._error_mapping:
|
||||||
|
self.message = self._error_mapping.get(self.code)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.message}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_error_message(cls, code=None):
|
||||||
|
return cls._error_mapping.get(code)
|
||||||
|
|
||||||
|
|
||||||
class NameserverErrorCodes(IntEnum):
|
class NameserverErrorCodes(IntEnum):
|
||||||
"""Used in the NameserverError class for
|
"""Used in the NameserverError class for
|
||||||
error mapping.
|
error mapping.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue