mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-30 01:10:04 +02:00
Merge branch 'main' of https://github.com/cisagov/manage.get.gov into es/3175-email-updates
This commit is contained in:
commit
bff19e3a28
19 changed files with 678 additions and 53 deletions
|
@ -103,3 +103,31 @@ response = registry._client.transport.receive()
|
||||||
```
|
```
|
||||||
|
|
||||||
This is helpful for debugging situations where epplib is not correctly or fully parsing the XML returned from the registry.
|
This is helpful for debugging situations where epplib is not correctly or fully parsing the XML returned from the registry.
|
||||||
|
|
||||||
|
### Adding in a expiring soon domain
|
||||||
|
The below scenario is if you are NOT in org model mode (`organization_feature` waffle flag is off).
|
||||||
|
|
||||||
|
1. Go to the `staging` sandbox and to `/admin`
|
||||||
|
2. Go to Domains and find a domain that is actually expired by sorting the Expiration Date column
|
||||||
|
3. Click into the domain to check the expiration date
|
||||||
|
4. Click into Manage Domain to double check the expiration date as well
|
||||||
|
5. Now hold onto that domain name, and save it for the command below
|
||||||
|
|
||||||
|
6. In a terminal, run these commands:
|
||||||
|
```
|
||||||
|
cf ssh getgov-<your-intials>
|
||||||
|
/tmp/lifecycle/shell
|
||||||
|
./manage.py shell
|
||||||
|
from registrar.models import Domain, DomainInvitation
|
||||||
|
from registrar.models import User
|
||||||
|
user = User.objects.filter(first_name="<your-first-name>")
|
||||||
|
domain = Domain.objects.get_or_create(name="<that-domain-here>")
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Go back to `/admin` and create Domain Information for that domain you just added in via the terminal
|
||||||
|
8. Go to Domain to find it
|
||||||
|
9. Click Manage Domain
|
||||||
|
10. Add yourself as domain manager
|
||||||
|
11. Go to the Registrar page and you should now see the expiring domain
|
||||||
|
|
||||||
|
If you want to be in the org model mode, turn the `organization_feature` waffle flag on, and add that domain via Django Admin to a portfolio to be able to view it.
|
|
@ -345,6 +345,11 @@ urlpatterns = [
|
||||||
views.DomainSecurityEmailView.as_view(),
|
views.DomainSecurityEmailView.as_view(),
|
||||||
name="domain-security-email",
|
name="domain-security-email",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"domain/<int:pk>/renewal",
|
||||||
|
views.DomainRenewalView.as_view(),
|
||||||
|
name="domain-renewal",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"domain/<int:pk>/users/add",
|
"domain/<int:pk>/users/add",
|
||||||
views.DomainAddUserView.as_view(),
|
views.DomainAddUserView.as_view(),
|
||||||
|
|
|
@ -10,6 +10,7 @@ from .domain import (
|
||||||
DomainDsdataFormset,
|
DomainDsdataFormset,
|
||||||
DomainDsdataForm,
|
DomainDsdataForm,
|
||||||
DomainSuborganizationForm,
|
DomainSuborganizationForm,
|
||||||
|
DomainRenewalForm,
|
||||||
)
|
)
|
||||||
from .portfolio import (
|
from .portfolio import (
|
||||||
PortfolioOrgAddressForm,
|
PortfolioOrgAddressForm,
|
||||||
|
|
|
@ -661,3 +661,15 @@ DomainDsdataFormset = formset_factory(
|
||||||
extra=0,
|
extra=0,
|
||||||
can_delete=True,
|
can_delete=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DomainRenewalForm(forms.Form):
|
||||||
|
"""Form making sure domain renewal ack is checked"""
|
||||||
|
|
||||||
|
is_policy_acknowledged = forms.BooleanField(
|
||||||
|
required=True,
|
||||||
|
label="I have read and agree to the requirements for operating a .gov domain.",
|
||||||
|
error_messages={
|
||||||
|
"required": "Check the box if you read and agree to the requirements for operating a .gov domain."
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
from django.core.management import BaseCommand, CommandError
|
from django.core.management import BaseCommand, CommandError
|
||||||
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper
|
from registrar.management.commands.utility.terminal_helper import TerminalColors, TerminalHelper
|
||||||
from registrar.models import DomainInformation, DomainRequest, FederalAgency, Suborganization, Portfolio, User
|
from registrar.models import DomainInformation, DomainRequest, FederalAgency, Suborganization, Portfolio, User
|
||||||
|
from registrar.models.utility.generic_helper import normalize_string
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -21,10 +22,21 @@ class Command(BaseCommand):
|
||||||
self.failed_portfolios = set()
|
self.failed_portfolios = set()
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
"""Add three arguments:
|
"""Add command line arguments to create federal portfolios.
|
||||||
1. agency_name => the value of FederalAgency.agency
|
|
||||||
2. --parse_requests => if true, adds the given portfolio to each related DomainRequest
|
Required (mutually exclusive) arguments:
|
||||||
3. --parse_domains => if true, adds the given portfolio to each related DomainInformation
|
--agency_name: Name of a specific FederalAgency to create a portfolio for
|
||||||
|
--branch: Federal branch to process ("executive", "legislative", or "judicial").
|
||||||
|
Creates portfolios for all FederalAgencies in that branch.
|
||||||
|
|
||||||
|
Required (at least one):
|
||||||
|
--parse_requests: Add the created portfolio(s) to related DomainRequest records
|
||||||
|
--parse_domains: Add the created portfolio(s) to related DomainInformation records
|
||||||
|
Note: You can use both --parse_requests and --parse_domains together
|
||||||
|
|
||||||
|
Optional (mutually exclusive with parse options):
|
||||||
|
--both: Shorthand for using both --parse_requests and --parse_domains
|
||||||
|
Cannot be used with --parse_requests or --parse_domains
|
||||||
"""
|
"""
|
||||||
group = parser.add_mutually_exclusive_group(required=True)
|
group = parser.add_mutually_exclusive_group(required=True)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
|
@ -78,12 +90,14 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
raise CommandError(f"Cannot find '{branch}' federal agencies in our database.")
|
raise CommandError(f"Cannot find '{branch}' federal agencies in our database.")
|
||||||
|
|
||||||
|
portfolios = []
|
||||||
for federal_agency in agencies:
|
for federal_agency in agencies:
|
||||||
message = f"Processing federal agency '{federal_agency.agency}'..."
|
message = f"Processing federal agency '{federal_agency.agency}'..."
|
||||||
TerminalHelper.colorful_logger(logger.info, TerminalColors.MAGENTA, message)
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.MAGENTA, message)
|
||||||
try:
|
try:
|
||||||
# C901 'Command.handle' is too complex (12)
|
# C901 'Command.handle' is too complex (12)
|
||||||
self.handle_populate_portfolio(federal_agency, parse_domains, parse_requests, both)
|
portfolio = self.handle_populate_portfolio(federal_agency, parse_domains, parse_requests, both)
|
||||||
|
portfolios.append(portfolio)
|
||||||
except Exception as exec:
|
except Exception as exec:
|
||||||
self.failed_portfolios.add(federal_agency)
|
self.failed_portfolios.add(federal_agency)
|
||||||
logger.error(exec)
|
logger.error(exec)
|
||||||
|
@ -99,9 +113,65 @@ class Command(BaseCommand):
|
||||||
display_as_str=True,
|
display_as_str=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# POST PROCESSING STEP: Remove the federal agency if it matches the portfolio name.
|
||||||
|
# We only do this for started domain requests.
|
||||||
|
if parse_requests or both:
|
||||||
|
TerminalHelper.prompt_for_execution(
|
||||||
|
system_exit_on_terminate=True,
|
||||||
|
prompt_message="This action will update domain requests even if they aren't on a portfolio.",
|
||||||
|
prompt_title=(
|
||||||
|
"POST PROCESS STEP: Do you want to clear federal agency on (related) started domain requests?"
|
||||||
|
),
|
||||||
|
verify_message=None,
|
||||||
|
)
|
||||||
|
self.post_process_started_domain_requests(agencies, portfolios)
|
||||||
|
|
||||||
|
def post_process_started_domain_requests(self, agencies, portfolios):
|
||||||
|
"""
|
||||||
|
Removes duplicate organization data by clearing federal_agency when it matches the portfolio name.
|
||||||
|
Only processes domain requests in STARTED status.
|
||||||
|
"""
|
||||||
|
message = "Removing duplicate portfolio and federal_agency values from domain requests..."
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.MAGENTA, message)
|
||||||
|
|
||||||
|
# For each request, clear the federal agency under these conditions:
|
||||||
|
# 1. A portfolio *already exists* with the same name as the federal agency.
|
||||||
|
# 2. Said portfolio (or portfolios) are only the ones specified at the start of the script.
|
||||||
|
# 3. The domain request is in status "started".
|
||||||
|
# Note: Both names are normalized so excess spaces are stripped and the string is lowercased.
|
||||||
|
domain_requests_to_update = DomainRequest.objects.filter(
|
||||||
|
federal_agency__in=agencies,
|
||||||
|
federal_agency__agency__isnull=False,
|
||||||
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
|
organization_name__isnull=False,
|
||||||
|
)
|
||||||
|
portfolio_set = {normalize_string(portfolio.organization_name) for portfolio in portfolios if portfolio}
|
||||||
|
|
||||||
|
# Update the request, assuming the given agency name matches the portfolio name
|
||||||
|
updated_requests = []
|
||||||
|
for req in domain_requests_to_update:
|
||||||
|
agency_name = normalize_string(req.federal_agency.agency)
|
||||||
|
if agency_name in portfolio_set:
|
||||||
|
req.federal_agency = None
|
||||||
|
updated_requests.append(req)
|
||||||
|
|
||||||
|
# Execute the update and Log the results
|
||||||
|
if TerminalHelper.prompt_for_execution(
|
||||||
|
system_exit_on_terminate=False,
|
||||||
|
prompt_message=(
|
||||||
|
f"{len(domain_requests_to_update)} domain requests will be updated. "
|
||||||
|
f"These records will be changed: {[str(req) for req in updated_requests]}"
|
||||||
|
),
|
||||||
|
prompt_title="Do you wish to commit this update to the database?",
|
||||||
|
):
|
||||||
|
DomainRequest.objects.bulk_update(updated_requests, ["federal_agency"])
|
||||||
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKBLUE, "Action completed successfully.")
|
||||||
|
|
||||||
def handle_populate_portfolio(self, federal_agency, parse_domains, parse_requests, both):
|
def handle_populate_portfolio(self, federal_agency, parse_domains, parse_requests, both):
|
||||||
"""Attempts to create a portfolio. If successful, this function will
|
"""Attempts to create a portfolio. If successful, this function will
|
||||||
also create new suborganizations"""
|
also create new suborganizations.
|
||||||
|
Returns the portfolio for the given federal_agency.
|
||||||
|
"""
|
||||||
portfolio, created = self.create_portfolio(federal_agency)
|
portfolio, created = self.create_portfolio(federal_agency)
|
||||||
if created:
|
if created:
|
||||||
self.create_suborganizations(portfolio, federal_agency)
|
self.create_suborganizations(portfolio, federal_agency)
|
||||||
|
@ -111,6 +181,8 @@ class Command(BaseCommand):
|
||||||
if parse_requests or both:
|
if parse_requests or both:
|
||||||
self.handle_portfolio_requests(portfolio, federal_agency)
|
self.handle_portfolio_requests(portfolio, federal_agency)
|
||||||
|
|
||||||
|
return portfolio
|
||||||
|
|
||||||
def create_portfolio(self, federal_agency):
|
def create_portfolio(self, federal_agency):
|
||||||
"""Creates a portfolio if it doesn't presently exist.
|
"""Creates a portfolio if it doesn't presently exist.
|
||||||
Returns portfolio, created."""
|
Returns portfolio, created."""
|
||||||
|
@ -172,7 +244,7 @@ class Command(BaseCommand):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check for existing suborgs on the current portfolio
|
# Check for existing suborgs on the current portfolio
|
||||||
existing_suborgs = Suborganization.objects.filter(name__in=org_names)
|
existing_suborgs = Suborganization.objects.filter(name__in=org_names, name__isnull=False)
|
||||||
if existing_suborgs.exists():
|
if existing_suborgs.exists():
|
||||||
message = f"Some suborganizations already exist for portfolio '{portfolio}'."
|
message = f"Some suborganizations already exist for portfolio '{portfolio}'."
|
||||||
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKBLUE, message)
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKBLUE, message)
|
||||||
|
@ -180,9 +252,7 @@ class Command(BaseCommand):
|
||||||
# Create new suborgs, as long as they don't exist in the db already
|
# Create new suborgs, as long as they don't exist in the db already
|
||||||
new_suborgs = []
|
new_suborgs = []
|
||||||
for name in org_names - set(existing_suborgs.values_list("name", flat=True)):
|
for name in org_names - set(existing_suborgs.values_list("name", flat=True)):
|
||||||
# Stored in variables due to linter wanting type information here.
|
if normalize_string(name) == normalize_string(portfolio.organization_name):
|
||||||
portfolio_name: str = portfolio.organization_name if portfolio.organization_name is not None else ""
|
|
||||||
if name is not None and name.lower() == portfolio_name.lower():
|
|
||||||
# You can use this to populate location information, when this occurs.
|
# You can use this to populate location information, when this occurs.
|
||||||
# However, this isn't needed for now so we can skip it.
|
# However, this isn't needed for now so we can skip it.
|
||||||
message = (
|
message = (
|
||||||
|
@ -229,12 +299,30 @@ class Command(BaseCommand):
|
||||||
# Get all suborg information and store it in a dict to avoid doing a db call
|
# Get all suborg information and store it in a dict to avoid doing a db call
|
||||||
suborgs = Suborganization.objects.filter(portfolio=portfolio).in_bulk(field_name="name")
|
suborgs = Suborganization.objects.filter(portfolio=portfolio).in_bulk(field_name="name")
|
||||||
for domain_request in domain_requests:
|
for domain_request in domain_requests:
|
||||||
|
# Set the portfolio
|
||||||
domain_request.portfolio = portfolio
|
domain_request.portfolio = portfolio
|
||||||
if domain_request.organization_name in suborgs:
|
|
||||||
domain_request.sub_organization = suborgs.get(domain_request.organization_name)
|
# Set suborg info
|
||||||
|
domain_request.sub_organization = suborgs.get(domain_request.organization_name, None)
|
||||||
|
if domain_request.sub_organization is None:
|
||||||
|
domain_request.requested_suborganization = normalize_string(
|
||||||
|
domain_request.organization_name, lowercase=False
|
||||||
|
)
|
||||||
|
domain_request.suborganization_city = normalize_string(domain_request.city, lowercase=False)
|
||||||
|
domain_request.suborganization_state_territory = domain_request.state_territory
|
||||||
|
|
||||||
self.updated_portfolios.add(portfolio)
|
self.updated_portfolios.add(portfolio)
|
||||||
|
|
||||||
DomainRequest.objects.bulk_update(domain_requests, ["portfolio", "sub_organization"])
|
DomainRequest.objects.bulk_update(
|
||||||
|
domain_requests,
|
||||||
|
[
|
||||||
|
"portfolio",
|
||||||
|
"sub_organization",
|
||||||
|
"requested_suborganization",
|
||||||
|
"suborganization_city",
|
||||||
|
"suborganization_state_territory",
|
||||||
|
],
|
||||||
|
)
|
||||||
message = f"Added portfolio '{portfolio}' to {len(domain_requests)} domain requests."
|
message = f"Added portfolio '{portfolio}' to {len(domain_requests)} domain requests."
|
||||||
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKGREEN, message)
|
TerminalHelper.colorful_logger(logger.info, TerminalColors.OKGREEN, message)
|
||||||
|
|
||||||
|
@ -242,6 +330,8 @@ class Command(BaseCommand):
|
||||||
"""
|
"""
|
||||||
Associate portfolio with domains for a federal agency.
|
Associate portfolio with domains for a federal agency.
|
||||||
Updates all relevant domain information records.
|
Updates all relevant domain information records.
|
||||||
|
|
||||||
|
Returns a queryset of DomainInformation objects, or None if nothing changed.
|
||||||
"""
|
"""
|
||||||
domain_infos = DomainInformation.objects.filter(federal_agency=federal_agency, portfolio__isnull=True)
|
domain_infos = DomainInformation.objects.filter(federal_agency=federal_agency, portfolio__isnull=True)
|
||||||
if not domain_infos.exists():
|
if not domain_infos.exists():
|
||||||
|
@ -257,8 +347,7 @@ class Command(BaseCommand):
|
||||||
suborgs = Suborganization.objects.filter(portfolio=portfolio).in_bulk(field_name="name")
|
suborgs = Suborganization.objects.filter(portfolio=portfolio).in_bulk(field_name="name")
|
||||||
for domain_info in domain_infos:
|
for domain_info in domain_infos:
|
||||||
domain_info.portfolio = portfolio
|
domain_info.portfolio = portfolio
|
||||||
if domain_info.organization_name in suborgs:
|
domain_info.sub_organization = suborgs.get(domain_info.organization_name, None)
|
||||||
domain_info.sub_organization = suborgs.get(domain_info.organization_name)
|
|
||||||
|
|
||||||
DomainInformation.objects.bulk_update(domain_infos, ["portfolio", "sub_organization"])
|
DomainInformation.objects.bulk_update(domain_infos, ["portfolio", "sub_organization"])
|
||||||
message = f"Added portfolio '{portfolio}' to {len(domain_infos)} domains."
|
message = f"Added portfolio '{portfolio}' to {len(domain_infos)} domains."
|
||||||
|
|
|
@ -326,9 +326,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
exp_date = self.registry_expiration_date
|
exp_date = self.registry_expiration_date
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# if no expiration date from registry, set it to today
|
# if no expiration date from registry, set it to today
|
||||||
logger.warning("current expiration date not set; setting to today")
|
logger.warning("current expiration date not set; setting to today", exc_info=True)
|
||||||
exp_date = date.today()
|
exp_date = date.today()
|
||||||
|
|
||||||
# create RenewDomain request
|
# create RenewDomain request
|
||||||
request = commands.RenewDomain(name=self.name, cur_exp_date=exp_date, period=epp.Period(length, unit))
|
request = commands.RenewDomain(name=self.name, cur_exp_date=exp_date, period=epp.Period(length, unit))
|
||||||
|
|
||||||
|
@ -338,13 +337,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self._cache["ex_date"] = registry.send(request, cleaned=True).res_data[0].ex_date
|
self._cache["ex_date"] = registry.send(request, cleaned=True).res_data[0].ex_date
|
||||||
self.expiration_date = self._cache["ex_date"]
|
self.expiration_date = self._cache["ex_date"]
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
except RegistryError as err:
|
except RegistryError as err:
|
||||||
# if registry error occurs, log the error, and raise it as well
|
# if registry error occurs, log the error, and raise it as well
|
||||||
logger.error(f"registry error renewing domain: {err}")
|
logger.error(f"Registry error renewing domain '{self.name}': {err}")
|
||||||
raise (err)
|
raise (err)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# exception raised during the save to registrar
|
# exception raised during the save to registrar
|
||||||
logger.error(f"error updating expiration date in registrar: {e}")
|
logger.error(f"Error updating expiration date for domain '{self.name}' in registrar: {e}")
|
||||||
raise (e)
|
raise (e)
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
|
@ -1575,7 +1575,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("Changing to DNS_NEEDED state")
|
logger.info("Changing to DNS_NEEDED state")
|
||||||
logger.info("able to transition to DNS_NEEDED state")
|
logger.info("able to transition to DNS_NEEDED state")
|
||||||
|
|
||||||
def get_state_help_text(self) -> str:
|
def get_state_help_text(self, request=None) -> str:
|
||||||
"""Returns a str containing additional information about a given state.
|
"""Returns a str containing additional information about a given state.
|
||||||
Returns custom content for when the domain itself is expired."""
|
Returns custom content for when the domain itself is expired."""
|
||||||
|
|
||||||
|
@ -1585,6 +1585,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
help_text = (
|
help_text = (
|
||||||
"This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
"This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
|
||||||
)
|
)
|
||||||
|
elif flag_is_active(request, "domain_renewal") and self.is_expiring():
|
||||||
|
help_text = "This domain will expire soon. Contact one of the listed domain managers to renew the domain."
|
||||||
else:
|
else:
|
||||||
help_text = Domain.State.get_help_text(self.state)
|
help_text = Domain.State.get_help_text(self.state)
|
||||||
|
|
||||||
|
|
|
@ -343,3 +343,13 @@ def value_of_attribute(obj, attribute_name: str):
|
||||||
if callable(value):
|
if callable(value):
|
||||||
value = value()
|
value = value()
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_string(string_to_normalize, lowercase=True):
|
||||||
|
"""Normalizes a given string. Returns a string without extra spaces, in all lowercase."""
|
||||||
|
if not isinstance(string_to_normalize, str):
|
||||||
|
logger.error(f"normalize_string => {string_to_normalize} is not type str.")
|
||||||
|
return string_to_normalize
|
||||||
|
|
||||||
|
new_string = " ".join(string_to_normalize.split())
|
||||||
|
return new_string.lower() if lowercase else new_string
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
|
||||||
|
|
||||||
{% block title %}{{ domain.name }} | {% endblock %}
|
{% block title %}{{ domain.name }} | {% endblock %}
|
||||||
|
|
||||||
|
@ -53,8 +55,11 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block domain_content %}
|
{% block domain_content %}
|
||||||
|
{% if request.path|endswith:"renewal"%}
|
||||||
|
<h1>Renew {{domain.name}} </h1>
|
||||||
|
{%else%}
|
||||||
<h1 class="break-word">Domain Overview</h1>
|
<h1 class="break-word">Domain Overview</h1>
|
||||||
|
{% endif%}
|
||||||
|
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -62,4 +67,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {# content #}
|
{% endblock %} {# content #}
|
|
@ -49,10 +49,18 @@
|
||||||
</span>
|
</span>
|
||||||
{% if domain.get_state_help_text %}
|
{% if domain.get_state_help_text %}
|
||||||
<div class="padding-top-1 text-primary-darker">
|
<div class="padding-top-1 text-primary-darker">
|
||||||
{% if has_domain_renewal_flag and domain.is_expiring and is_domain_manager %}
|
{% if has_domain_renewal_flag and domain.is_expired and is_domain_manager %}
|
||||||
This domain will expire soon. <a href="/not-available-yet">Renew to maintain access.</a>
|
This domain has expired, but it is still online.
|
||||||
|
{% url 'domain-renewal' pk=domain.id as url %}
|
||||||
|
<a href="{{ url }}">Renew to maintain access.</a>
|
||||||
|
{% elif has_domain_renewal_flag and domain.is_expiring and is_domain_manager %}
|
||||||
|
This domain will expire soon.
|
||||||
|
{% url 'domain-renewal' pk=domain.id as url %}
|
||||||
|
<a href="{{ url }}">Renew to maintain access.</a>
|
||||||
{% elif has_domain_renewal_flag and domain.is_expiring and is_portfolio_user %}
|
{% elif has_domain_renewal_flag and domain.is_expiring and is_portfolio_user %}
|
||||||
This domain will expire soon. Contact one of the listed domain managers to renew the domain.
|
This domain will expire soon. Contact one of the listed domain managers to renew the domain.
|
||||||
|
{% elif has_domain_renewal_flag and domain.is_expired and is_portfolio_user %}
|
||||||
|
This domain has expired, but it is still online. Contact one of the listed domain managers to renew the domain.
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ domain.get_state_help_text }}
|
{{ domain.get_state_help_text }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
140
src/registrar/templates/domain_renewal.html
Normal file
140
src/registrar/templates/domain_renewal.html
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
{% extends "domain_base.html" %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
{% load custom_filters %}
|
||||||
|
|
||||||
|
{% block domain_content %}
|
||||||
|
{% block breadcrumb %}
|
||||||
|
|
||||||
|
<!-- Banner for if_policy_acknowledged -->
|
||||||
|
{% if form.is_policy_acknowledged.errors %}
|
||||||
|
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
|
||||||
|
<div class="usa-alert__body">
|
||||||
|
{% for error in form.is_policy_acknowledged.errors %}
|
||||||
|
<p class="usa-alert__text">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if portfolio %}
|
||||||
|
<!-- Navigation breadcrumbs -->
|
||||||
|
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain breadcrumb">
|
||||||
|
<ol class="usa-breadcrumb__list">
|
||||||
|
<li class="usa-breadcrumb__list-item">
|
||||||
|
<a href="{% url 'domains' %}" class="usa-breadcrumb__link"><span>Domains</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="usa-breadcrumb__list-item">
|
||||||
|
<a href="{% url 'domain' pk=domain.id %}" class="usa-breadcrumb__link"><span>{{domain.name}}</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
|
||||||
|
<span>Renewal Form</span>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock breadcrumb %}
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
<div class="margin-top-4 tablet:grid-col-10">
|
||||||
|
<h2 class="text-bold text-primary-dark domain-name-wrap">Confirm the following information for accuracy</h2>
|
||||||
|
<p>Review these details below. We <a href="https://get.gov/domains/requirements/#what-.gov-domain-registrants-must-do" class="usa-link">
|
||||||
|
require</a> that you maintain accurate information for the domain.
|
||||||
|
The details you provide will only be used to support the administration of .gov and won't be made public.
|
||||||
|
</p>
|
||||||
|
<p>If you would like to retire your domain instead, please <a href="https://get.gov/contact/" class="usa-link">
|
||||||
|
contact us</a>. </p>
|
||||||
|
<p><em>Required fields are marked with an asterisk (<abbr class="usa-hint usa-hint--required" title="required">*</abbr>).</em>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
{% url 'user-profile' as url %}
|
||||||
|
{% include "includes/summary_item.html" with title='Your contact information' value=request.user edit_link=url editable=is_editable contact='true' %}
|
||||||
|
|
||||||
|
{% if analyst_action != 'edit' or analyst_action_location != domain.pk %}
|
||||||
|
{% if is_portfolio_user and not is_domain_manager %}
|
||||||
|
<div class="usa-alert usa-alert--info usa-alert--slim">
|
||||||
|
<div class="usa-alert__body">
|
||||||
|
<p class="usa-alert__text ">
|
||||||
|
You don't have access to manage {{domain.name}}. If you need to make updates, contact one of the listed domain managers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% url 'domain-security-email' pk=domain.id as url %}
|
||||||
|
{% if security_email is not None and security_email not in hidden_security_emails%}
|
||||||
|
{% include "includes/summary_item.html" with title='Security email' value=security_email custom_text_for_value_none='We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain.' edit_link=url editable=is_editable %}
|
||||||
|
{% else %}
|
||||||
|
{% include "includes/summary_item.html" with title='Security email' value='None provided' custom_text_for_value_none='We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain.' edit_link=url editable=is_editable %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% url 'domain-users' pk=domain.id as url %}
|
||||||
|
{% if portfolio %}
|
||||||
|
{% include "includes/summary_item.html" with title='Domain managers' domain_permissions=True value=domain edit_link=url editable=is_editable %}
|
||||||
|
{% else %}
|
||||||
|
{% include "includes/summary_item.html" with title='Domain managers' list=True users=True value=domain.permissions.all edit_link=url editable=is_editable %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="border-top-1px border-primary-dark padding-top-1 margin-top-3 margin-bottom-2">
|
||||||
|
|
||||||
|
<fieldset class="usa-fieldset">
|
||||||
|
<legend>
|
||||||
|
<h3 class="summary-item__title
|
||||||
|
font-sans-md
|
||||||
|
text-primary-dark
|
||||||
|
text-semibold
|
||||||
|
margin-top-0
|
||||||
|
margin-bottom-05
|
||||||
|
padding-right-1">
|
||||||
|
Acknowledgement of .gov domain requirements </h3>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'domain-renewal' pk=domain.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="usa-checkbox">
|
||||||
|
|
||||||
|
{% if form.is_policy_acknowledged.errors %}
|
||||||
|
{% for error in form.is_policy_acknowledged.errors %}
|
||||||
|
<div class="usa-error-message display-flex" role="alert">
|
||||||
|
<svg class="usa-icon usa-icon--large" focusable="true" role="img" aria-label="Error">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#error"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="margin-left-05">{{ error }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input type="hidden" name="is_policy_acknowledged" value="False">
|
||||||
|
|
||||||
|
<input
|
||||||
|
class="usa-checkbox__input"
|
||||||
|
id="renewal-checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
name="is_policy_acknowledged"
|
||||||
|
value="True"
|
||||||
|
{% if form.is_policy_acknowledged.value %}checked{% endif %}
|
||||||
|
>
|
||||||
|
<label class="usa-checkbox__label" for="renewal-checkbox">
|
||||||
|
I read and agree to the
|
||||||
|
<a href="https://get.gov/domains/requirements/" class="usa-link">
|
||||||
|
requirements for operating a .gov domain
|
||||||
|
</a>.
|
||||||
|
<abbr class="usa-hint usa-hint--required" title="required">*</abbr>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="submit_button"
|
||||||
|
value="next"
|
||||||
|
class="usa-button margin-top-3"
|
||||||
|
> Submit
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</div> <!-- End of the acknowledgement section div -->
|
||||||
|
</div>
|
||||||
|
{% endblock %} {# domain_content #}
|
|
@ -80,7 +80,16 @@
|
||||||
{% include "includes/domain_sidenav_item.html" with item_text="Domain managers" %}
|
{% include "includes/domain_sidenav_item.html" with item_text="Domain managers" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
|
||||||
|
{% if has_domain_renewal_flag and is_domain_manager%}
|
||||||
|
{% if domain.is_expiring or domain.is_expired %}
|
||||||
|
{% with url_name="domain-renewal" %}
|
||||||
|
{% include "includes/domain_sidenav_item.html" with item_text="Renewal form" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
|
@ -172,7 +172,7 @@
|
||||||
>Deleted</label
|
>Deleted</label
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{% if has_domain_renewal_flag and num_expiring_domains > 0 %}
|
{% if has_domain_renewal_flag %}
|
||||||
<div class="usa-checkbox">
|
<div class="usa-checkbox">
|
||||||
<input
|
<input
|
||||||
class="usa-checkbox__input"
|
class="usa-checkbox__input"
|
||||||
|
|
|
@ -127,15 +127,15 @@
|
||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="margin-top-0 margin-bottom-0">
|
{% if custom_text_for_value_none %}
|
||||||
|
<p class="margin-top-0 text-base-dark">{{ custom_text_for_value_none }}</p>
|
||||||
|
{% endif %}
|
||||||
{% if value %}
|
{% if value %}
|
||||||
{{ value }}
|
{{ value }}
|
||||||
{% elif custom_text_for_value_none %}
|
{% endif %}
|
||||||
{{ custom_text_for_value_none }}
|
{% if not value %}
|
||||||
{% else %}
|
|
||||||
None
|
None
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,7 @@ def is_domain_subpage(path):
|
||||||
"domain-users-add",
|
"domain-users-add",
|
||||||
"domain-request-delete",
|
"domain-request-delete",
|
||||||
"domain-user-delete",
|
"domain-user-delete",
|
||||||
|
"domain-renewal",
|
||||||
"invitation-cancel",
|
"invitation-cancel",
|
||||||
]
|
]
|
||||||
return get_url_name(path) in url_names
|
return get_url_name(path) in url_names
|
||||||
|
|
|
@ -1039,6 +1039,8 @@ def completed_domain_request( # noqa
|
||||||
federal_agency=None,
|
federal_agency=None,
|
||||||
federal_type=None,
|
federal_type=None,
|
||||||
action_needed_reason=None,
|
action_needed_reason=None,
|
||||||
|
city=None,
|
||||||
|
state_territory=None,
|
||||||
portfolio=None,
|
portfolio=None,
|
||||||
organization_name=None,
|
organization_name=None,
|
||||||
sub_organization=None,
|
sub_organization=None,
|
||||||
|
@ -1081,7 +1083,7 @@ def completed_domain_request( # noqa
|
||||||
organization_name=organization_name if organization_name else "Testorg",
|
organization_name=organization_name if organization_name else "Testorg",
|
||||||
address_line1="address 1",
|
address_line1="address 1",
|
||||||
address_line2="address 2",
|
address_line2="address 2",
|
||||||
state_territory="NY",
|
state_territory="NY" if not state_territory else state_territory,
|
||||||
zipcode="10002",
|
zipcode="10002",
|
||||||
senior_official=so,
|
senior_official=so,
|
||||||
requested_domain=domain,
|
requested_domain=domain,
|
||||||
|
@ -1090,6 +1092,10 @@ def completed_domain_request( # noqa
|
||||||
investigator=investigator,
|
investigator=investigator,
|
||||||
federal_agency=federal_agency,
|
federal_agency=federal_agency,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if city:
|
||||||
|
domain_request_kwargs["city"] = city
|
||||||
|
|
||||||
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"
|
||||||
if has_anything_else:
|
if has_anything_else:
|
||||||
|
|
|
@ -1516,6 +1516,91 @@ class TestCreateFederalPortfolio(TestCase):
|
||||||
):
|
):
|
||||||
call_command("create_federal_portfolio", **kwargs)
|
call_command("create_federal_portfolio", **kwargs)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_post_process_started_domain_requests_existing_portfolio(self):
|
||||||
|
"""Ensures that federal agency is cleared when agency name matches portfolio name.
|
||||||
|
As the name implies, this implicitly tests the "post_process_started_domain_requests" function.
|
||||||
|
"""
|
||||||
|
federal_agency_2 = FederalAgency.objects.create(agency="Sugarcane", federal_type=BranchChoices.EXECUTIVE)
|
||||||
|
|
||||||
|
# Test records with portfolios and no org names
|
||||||
|
# Create a portfolio. This script skips over "started"
|
||||||
|
portfolio = Portfolio.objects.create(organization_name="Sugarcane", creator=self.user)
|
||||||
|
# Create a domain request with matching org name
|
||||||
|
matching_request = completed_domain_request(
|
||||||
|
name="matching.gov",
|
||||||
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=federal_agency_2,
|
||||||
|
user=self.user,
|
||||||
|
portfolio=portfolio,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a request not in started (no change should occur)
|
||||||
|
matching_request_in_wrong_status = completed_domain_request(
|
||||||
|
name="kinda-matching.gov",
|
||||||
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.federal_agency,
|
||||||
|
user=self.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.run_create_federal_portfolio(agency_name="Sugarcane", parse_requests=True)
|
||||||
|
self.run_create_federal_portfolio(agency_name="Test Federal Agency", parse_requests=True)
|
||||||
|
|
||||||
|
# Refresh from db
|
||||||
|
matching_request.refresh_from_db()
|
||||||
|
matching_request_in_wrong_status.refresh_from_db()
|
||||||
|
|
||||||
|
# Request with matching name should have federal_agency cleared
|
||||||
|
self.assertIsNone(matching_request.federal_agency)
|
||||||
|
self.assertIsNotNone(matching_request.portfolio)
|
||||||
|
self.assertEqual(matching_request.portfolio.organization_name, "Sugarcane")
|
||||||
|
|
||||||
|
# Request with matching name but wrong state should keep its federal agency
|
||||||
|
self.assertEqual(matching_request_in_wrong_status.federal_agency, self.federal_agency)
|
||||||
|
self.assertIsNotNone(matching_request_in_wrong_status.portfolio)
|
||||||
|
self.assertEqual(matching_request_in_wrong_status.portfolio.organization_name, "Test Federal Agency")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_post_process_started_domain_requests(self):
|
||||||
|
"""Tests that federal agency is cleared when agency name
|
||||||
|
matches an existing portfolio's name, even if the domain request isn't
|
||||||
|
directly on that portfolio."""
|
||||||
|
|
||||||
|
federal_agency_2 = FederalAgency.objects.create(agency="Sugarcane", federal_type=BranchChoices.EXECUTIVE)
|
||||||
|
|
||||||
|
# Create a request with matching federal_agency name but no direct portfolio association
|
||||||
|
matching_agency_request = completed_domain_request(
|
||||||
|
name="agency-match.gov",
|
||||||
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=federal_agency_2,
|
||||||
|
user=self.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a control request that shouldn't match
|
||||||
|
non_matching_request = completed_domain_request(
|
||||||
|
name="no-match.gov",
|
||||||
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.federal_agency,
|
||||||
|
user=self.user,
|
||||||
|
)
|
||||||
|
|
||||||
|
# We expect the matching agency to have its fed agency cleared.
|
||||||
|
self.run_create_federal_portfolio(agency_name="Sugarcane", parse_requests=True)
|
||||||
|
matching_agency_request.refresh_from_db()
|
||||||
|
non_matching_request.refresh_from_db()
|
||||||
|
|
||||||
|
# Request with matching agency name should have federal_agency cleared
|
||||||
|
self.assertIsNone(matching_agency_request.federal_agency)
|
||||||
|
|
||||||
|
# Non-matching request should keep its federal_agency
|
||||||
|
self.assertIsNotNone(non_matching_request.federal_agency)
|
||||||
|
self.assertEqual(non_matching_request.federal_agency, self.federal_agency)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
def test_create_single_portfolio(self):
|
def test_create_single_portfolio(self):
|
||||||
"""Test portfolio creation with suborg and senior official."""
|
"""Test portfolio creation with suborg and senior official."""
|
||||||
self.run_create_federal_portfolio(agency_name="Test Federal Agency", parse_requests=True)
|
self.run_create_federal_portfolio(agency_name="Test Federal Agency", parse_requests=True)
|
||||||
|
@ -1588,6 +1673,34 @@ class TestCreateFederalPortfolio(TestCase):
|
||||||
self.assertTrue(all([creator == User.get_default_user() for creator in creators]))
|
self.assertTrue(all([creator == User.get_default_user() for creator in creators]))
|
||||||
self.assertTrue(all([note == "Auto-generated record" for note in notes]))
|
self.assertTrue(all([note == "Auto-generated record" for note in notes]))
|
||||||
|
|
||||||
|
def test_script_adds_requested_suborganization_information(self):
|
||||||
|
"""Tests that the script adds the requested suborg fields for domain requests"""
|
||||||
|
# Create a new domain request with some errant spacing
|
||||||
|
custom_suborg_request = completed_domain_request(
|
||||||
|
name="custom_org.gov",
|
||||||
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
|
federal_agency=self.executive_agency_2,
|
||||||
|
user=self.user,
|
||||||
|
organization_name=" requested org name ",
|
||||||
|
city="Austin ",
|
||||||
|
state_territory=DomainRequest.StateTerritoryChoices.TEXAS,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIsNone(custom_suborg_request.requested_suborganization)
|
||||||
|
self.assertIsNone(custom_suborg_request.suborganization_city)
|
||||||
|
self.assertIsNone(custom_suborg_request.suborganization_state_territory)
|
||||||
|
|
||||||
|
# Run the script and test it
|
||||||
|
self.run_create_federal_portfolio(branch="executive", parse_requests=True)
|
||||||
|
custom_suborg_request.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertEqual(custom_suborg_request.requested_suborganization, "requested org name")
|
||||||
|
self.assertEqual(custom_suborg_request.suborganization_city, "Austin")
|
||||||
|
self.assertEqual(
|
||||||
|
custom_suborg_request.suborganization_state_territory, DomainRequest.StateTerritoryChoices.TEXAS
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_multiple_portfolios_for_branch_executive(self):
|
def test_create_multiple_portfolios_for_branch_executive(self):
|
||||||
"""Tests creating all portfolios under a given branch"""
|
"""Tests creating all portfolios under a given branch"""
|
||||||
federal_choice = DomainRequest.OrganizationChoices.FEDERAL
|
federal_choice = DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
|
|
@ -439,35 +439,47 @@ class TestDomainDetailDomainRenewal(TestDomainOverview):
|
||||||
username="usertest",
|
username="usertest",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.expiringdomain, _ = Domain.objects.get_or_create(
|
self.domaintorenew, _ = Domain.objects.get_or_create(
|
||||||
name="expiringdomain.gov",
|
name="domainrenewal.gov",
|
||||||
)
|
)
|
||||||
|
|
||||||
UserDomainRole.objects.get_or_create(
|
UserDomainRole.objects.get_or_create(
|
||||||
user=self.user, domain=self.expiringdomain, role=UserDomainRole.Roles.MANAGER
|
user=self.user, domain=self.domaintorenew, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.expiringdomain)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=self.domaintorenew)
|
||||||
|
|
||||||
self.portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
|
self.portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
|
||||||
|
|
||||||
self.user.save()
|
self.user.save()
|
||||||
|
|
||||||
def custom_is_expired(self):
|
def expiration_date_one_year_out(self):
|
||||||
|
todays_date = datetime.today()
|
||||||
|
new_expiration_date = todays_date.replace(year=todays_date.year + 1)
|
||||||
|
return new_expiration_date
|
||||||
|
|
||||||
|
def custom_is_expired_false(self):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def custom_is_expired_true(self):
|
||||||
|
return True
|
||||||
|
|
||||||
def custom_is_expiring(self):
|
def custom_is_expiring(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def custom_renew_domain(self):
|
||||||
|
self.domain_with_ip.expiration_date = self.expiration_date_one_year_out()
|
||||||
|
self.domain_with_ip.save()
|
||||||
|
|
||||||
@override_flag("domain_renewal", active=True)
|
@override_flag("domain_renewal", active=True)
|
||||||
def test_expiring_domain_on_detail_page_as_domain_manager(self):
|
def test_expiring_domain_on_detail_page_as_domain_manager(self):
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||||
Domain, "is_expired", self.custom_is_expired
|
Domain, "is_expired", self.custom_is_expired_false
|
||||||
):
|
):
|
||||||
self.assertEquals(self.expiringdomain.state, Domain.State.UNKNOWN)
|
self.assertEquals(self.domaintorenew.state, Domain.State.UNKNOWN)
|
||||||
detail_page = self.client.get(
|
detail_page = self.client.get(
|
||||||
reverse("domain", kwargs={"pk": self.expiringdomain.id}),
|
reverse("domain", kwargs={"pk": self.domaintorenew.id}),
|
||||||
)
|
)
|
||||||
self.assertContains(detail_page, "Expiring soon")
|
self.assertContains(detail_page, "Expiring soon")
|
||||||
|
|
||||||
|
@ -498,17 +510,17 @@ class TestDomainDetailDomainRenewal(TestDomainOverview):
|
||||||
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
expiringdomain2, _ = Domain.objects.get_or_create(name="bogusdomain2.gov")
|
domaintorenew2, _ = Domain.objects.get_or_create(name="bogusdomain2.gov")
|
||||||
DomainInformation.objects.get_or_create(
|
DomainInformation.objects.get_or_create(
|
||||||
creator=non_dom_manage_user, domain=expiringdomain2, portfolio=self.portfolio
|
creator=non_dom_manage_user, domain=domaintorenew2, portfolio=self.portfolio
|
||||||
)
|
)
|
||||||
non_dom_manage_user.refresh_from_db()
|
non_dom_manage_user.refresh_from_db()
|
||||||
self.client.force_login(non_dom_manage_user)
|
self.client.force_login(non_dom_manage_user)
|
||||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||||
Domain, "is_expired", self.custom_is_expired
|
Domain, "is_expired", self.custom_is_expired_false
|
||||||
):
|
):
|
||||||
detail_page = self.client.get(
|
detail_page = self.client.get(
|
||||||
reverse("domain", kwargs={"pk": expiringdomain2.id}),
|
reverse("domain", kwargs={"pk": domaintorenew2.id}),
|
||||||
)
|
)
|
||||||
self.assertContains(detail_page, "Contact one of the listed domain managers to renew the domain.")
|
self.assertContains(detail_page, "Contact one of the listed domain managers to renew the domain.")
|
||||||
|
|
||||||
|
@ -517,20 +529,164 @@ class TestDomainDetailDomainRenewal(TestDomainOverview):
|
||||||
def test_expiring_domain_on_detail_page_in_org_model_as_a_domain_manager(self):
|
def test_expiring_domain_on_detail_page_in_org_model_as_a_domain_manager(self):
|
||||||
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org2", creator=self.user)
|
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org2", creator=self.user)
|
||||||
|
|
||||||
expiringdomain3, _ = Domain.objects.get_or_create(name="bogusdomain3.gov")
|
domaintorenew3, _ = Domain.objects.get_or_create(name="bogusdomain3.gov")
|
||||||
|
|
||||||
UserDomainRole.objects.get_or_create(user=self.user, domain=expiringdomain3, role=UserDomainRole.Roles.MANAGER)
|
UserDomainRole.objects.get_or_create(user=self.user, domain=domaintorenew3, role=UserDomainRole.Roles.MANAGER)
|
||||||
DomainInformation.objects.get_or_create(creator=self.user, domain=expiringdomain3, portfolio=portfolio)
|
DomainInformation.objects.get_or_create(creator=self.user, domain=domaintorenew3, portfolio=portfolio)
|
||||||
self.user.refresh_from_db()
|
self.user.refresh_from_db()
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||||
Domain, "is_expired", self.custom_is_expired
|
Domain, "is_expired", self.custom_is_expired_false
|
||||||
):
|
):
|
||||||
detail_page = self.client.get(
|
detail_page = self.client.get(
|
||||||
reverse("domain", kwargs={"pk": expiringdomain3.id}),
|
reverse("domain", kwargs={"pk": domaintorenew3.id}),
|
||||||
)
|
)
|
||||||
self.assertContains(detail_page, "Renew to maintain access")
|
self.assertContains(detail_page, "Renew to maintain access")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_domain_renewal_form_and_sidebar_expiring(self):
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||||
|
Domain, "is_expiring", self.custom_is_expiring
|
||||||
|
):
|
||||||
|
# Grab the detail page
|
||||||
|
detail_page = self.client.get(
|
||||||
|
reverse("domain", kwargs={"pk": self.domaintorenew.id}),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure we see the link as a domain manager
|
||||||
|
self.assertContains(detail_page, "Renew to maintain access")
|
||||||
|
|
||||||
|
# Make sure we can see Renewal form on the sidebar since it's expiring
|
||||||
|
self.assertContains(detail_page, "Renewal form")
|
||||||
|
|
||||||
|
# Grab link to the renewal page
|
||||||
|
renewal_form_url = reverse("domain-renewal", kwargs={"pk": self.domaintorenew.id})
|
||||||
|
self.assertContains(detail_page, f'href="{renewal_form_url}"')
|
||||||
|
|
||||||
|
# Simulate clicking the link
|
||||||
|
response = self.client.get(renewal_form_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, f"Renew {self.domaintorenew.name}")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_domain_renewal_form_and_sidebar_expired(self):
|
||||||
|
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
with patch.object(Domain, "is_expired", self.custom_is_expired_true), patch.object(
|
||||||
|
Domain, "is_expired", self.custom_is_expired_true
|
||||||
|
):
|
||||||
|
# Grab the detail page
|
||||||
|
detail_page = self.client.get(
|
||||||
|
reverse("domain", kwargs={"pk": self.domaintorenew.id}),
|
||||||
|
)
|
||||||
|
|
||||||
|
print("puglesss", self.domaintorenew.is_expired)
|
||||||
|
# Make sure we see the link as a domain manager
|
||||||
|
self.assertContains(detail_page, "Renew to maintain access")
|
||||||
|
|
||||||
|
# Make sure we can see Renewal form on the sidebar since it's expired
|
||||||
|
self.assertContains(detail_page, "Renewal form")
|
||||||
|
|
||||||
|
# Grab link to the renewal page
|
||||||
|
renewal_form_url = reverse("domain-renewal", kwargs={"pk": self.domaintorenew.id})
|
||||||
|
self.assertContains(detail_page, f'href="{renewal_form_url}"')
|
||||||
|
|
||||||
|
# Simulate clicking the link
|
||||||
|
response = self.client.get(renewal_form_url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, f"Renew {self.domaintorenew.name}")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_domain_renewal_form_your_contact_info_edit(self):
|
||||||
|
with less_console_noise():
|
||||||
|
# Start on the Renewal page for the domain
|
||||||
|
renewal_page = self.app.get(reverse("domain-renewal", kwargs={"pk": self.domain_with_ip.id}))
|
||||||
|
|
||||||
|
# Verify we see "Your contact information" on the renewal form
|
||||||
|
self.assertContains(renewal_page, "Your contact information")
|
||||||
|
|
||||||
|
# Verify that the "Edit" button for Your contact is there and links to correct URL
|
||||||
|
edit_button_url = reverse("user-profile")
|
||||||
|
self.assertContains(renewal_page, f'href="{edit_button_url}"')
|
||||||
|
|
||||||
|
# Simulate clicking on edit button
|
||||||
|
edit_page = renewal_page.click(href=edit_button_url, index=1)
|
||||||
|
self.assertEqual(edit_page.status_code, 200)
|
||||||
|
self.assertContains(edit_page, "Review the details below and update any required information")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_domain_renewal_form_security_email_edit(self):
|
||||||
|
with less_console_noise():
|
||||||
|
# Start on the Renewal page for the domain
|
||||||
|
renewal_page = self.app.get(reverse("domain-renewal", kwargs={"pk": self.domain_with_ip.id}))
|
||||||
|
|
||||||
|
# Verify we see "Security email" on the renewal form
|
||||||
|
self.assertContains(renewal_page, "Security email")
|
||||||
|
|
||||||
|
# Verify we see "strong recommend" blurb
|
||||||
|
self.assertContains(renewal_page, "We strongly recommend that you provide a security email.")
|
||||||
|
|
||||||
|
# Verify that the "Edit" button for Security email is there and links to correct URL
|
||||||
|
edit_button_url = reverse("domain-security-email", kwargs={"pk": self.domain_with_ip.id})
|
||||||
|
self.assertContains(renewal_page, f'href="{edit_button_url}"')
|
||||||
|
|
||||||
|
# Simulate clicking on edit button
|
||||||
|
edit_page = renewal_page.click(href=edit_button_url, index=1)
|
||||||
|
self.assertEqual(edit_page.status_code, 200)
|
||||||
|
self.assertContains(edit_page, "A security contact should be capable of evaluating")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_domain_renewal_form_domain_manager_edit(self):
|
||||||
|
with less_console_noise():
|
||||||
|
# Start on the Renewal page for the domain
|
||||||
|
renewal_page = self.app.get(reverse("domain-renewal", kwargs={"pk": self.domain_with_ip.id}))
|
||||||
|
|
||||||
|
# Verify we see "Domain managers" on the renewal form
|
||||||
|
self.assertContains(renewal_page, "Domain managers")
|
||||||
|
|
||||||
|
# Verify that the "Edit" button for Domain managers is there and links to correct URL
|
||||||
|
edit_button_url = reverse("domain-users", kwargs={"pk": self.domain_with_ip.id})
|
||||||
|
self.assertContains(renewal_page, f'href="{edit_button_url}"')
|
||||||
|
|
||||||
|
# Simulate clicking on edit button
|
||||||
|
edit_page = renewal_page.click(href=edit_button_url, index=1)
|
||||||
|
self.assertEqual(edit_page.status_code, 200)
|
||||||
|
self.assertContains(edit_page, "Domain managers can update all information related to a domain")
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_ack_checkbox_not_checked(self):
|
||||||
|
|
||||||
|
# Grab the renewal URL
|
||||||
|
renewal_url = reverse("domain-renewal", kwargs={"pk": self.domain_with_ip.id})
|
||||||
|
|
||||||
|
# Test that the checkbox is not checked
|
||||||
|
response = self.client.post(renewal_url, data={"submit_button": "next"})
|
||||||
|
|
||||||
|
error_message = "Check the box if you read and agree to the requirements for operating a .gov domain."
|
||||||
|
self.assertContains(response, error_message)
|
||||||
|
|
||||||
|
@override_flag("domain_renewal", active=True)
|
||||||
|
def test_ack_checkbox_checked(self):
|
||||||
|
|
||||||
|
# Grab the renewal URL
|
||||||
|
with patch.object(Domain, "renew_domain", self.custom_renew_domain):
|
||||||
|
renewal_url = reverse("domain-renewal", kwargs={"pk": self.domain_with_ip.id})
|
||||||
|
|
||||||
|
# Click the check, and submit
|
||||||
|
response = self.client.post(renewal_url, data={"is_policy_acknowledged": "on", "submit_button": "next"})
|
||||||
|
|
||||||
|
# Check that it redirects after a successfully submits
|
||||||
|
self.assertRedirects(response, reverse("domain", kwargs={"pk": self.domain_with_ip.id}))
|
||||||
|
|
||||||
|
# Check for the updated expiration
|
||||||
|
formatted_new_expiration_date = self.expiration_date_one_year_out().strftime("%b. %-d, %Y")
|
||||||
|
redirect_response = self.client.get(reverse("domain", kwargs={"pk": self.domain_with_ip.id}), follow=True)
|
||||||
|
self.assertContains(redirect_response, formatted_new_expiration_date)
|
||||||
|
|
||||||
|
|
||||||
class TestDomainManagers(TestDomainOverview):
|
class TestDomainManagers(TestDomainOverview):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -2660,7 +2816,6 @@ class TestDomainRenewal(TestWithUser):
|
||||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
domains_page = self.client.get("/")
|
domains_page = self.client.get("/")
|
||||||
self.assertNotContains(domains_page, "Expiring soon")
|
|
||||||
self.assertNotContains(domains_page, "will expire soon")
|
self.assertNotContains(domains_page, "will expire soon")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
|
@ -2698,5 +2853,4 @@ class TestDomainRenewal(TestWithUser):
|
||||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
domains_page = self.client.get("/")
|
domains_page = self.client.get("/")
|
||||||
self.assertNotContains(domains_page, "Expiring soon")
|
|
||||||
self.assertNotContains(domains_page, "will expire soon")
|
self.assertNotContains(domains_page, "will expire soon")
|
||||||
|
|
|
@ -14,6 +14,7 @@ from .domain import (
|
||||||
DomainInvitationCancelView,
|
DomainInvitationCancelView,
|
||||||
DomainDeleteUserView,
|
DomainDeleteUserView,
|
||||||
PrototypeDomainDNSRecordView,
|
PrototypeDomainDNSRecordView,
|
||||||
|
DomainRenewalView,
|
||||||
)
|
)
|
||||||
from .user_profile import UserProfileView, FinishProfileSetupView
|
from .user_profile import UserProfileView, FinishProfileSetupView
|
||||||
from .health import *
|
from .health import *
|
||||||
|
|
|
@ -12,11 +12,11 @@ from django.contrib import messages
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.db import IntegrityError
|
from django.db import IntegrityError
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect, render, get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from registrar.forms.domain import DomainSuborganizationForm
|
from registrar.forms.domain import DomainSuborganizationForm, DomainRenewalForm
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
Domain,
|
Domain,
|
||||||
DomainRequest,
|
DomainRequest,
|
||||||
|
@ -311,6 +311,47 @@ class DomainView(DomainBaseView):
|
||||||
self._update_session_with_domain()
|
self._update_session_with_domain()
|
||||||
|
|
||||||
|
|
||||||
|
class DomainRenewalView(DomainView):
|
||||||
|
"""Domain detail overview page."""
|
||||||
|
|
||||||
|
template_name = "domain_renewal.html"
|
||||||
|
|
||||||
|
def post(self, request, pk):
|
||||||
|
|
||||||
|
domain = get_object_or_404(Domain, id=pk)
|
||||||
|
|
||||||
|
form = DomainRenewalForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
# check for key in the post request data
|
||||||
|
if "submit_button" in request.POST:
|
||||||
|
try:
|
||||||
|
domain.renew_domain()
|
||||||
|
messages.success(request, "This domain has been renewed for one year.")
|
||||||
|
except Exception:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
"This domain has not been renewed for one year, "
|
||||||
|
"please email help@get.gov if this problem persists.",
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(reverse("domain", kwargs={"pk": pk}))
|
||||||
|
|
||||||
|
# if not valid, render the template with error messages
|
||||||
|
# passing editable, has_domain_renewal_flag, and is_editable for re-render
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"domain_renewal.html",
|
||||||
|
{
|
||||||
|
"domain": domain,
|
||||||
|
"form": form,
|
||||||
|
"is_editable": True,
|
||||||
|
"has_domain_renewal_flag": True,
|
||||||
|
"is_domain_manager": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DomainOrgNameAddressView(DomainFormBaseView):
|
class DomainOrgNameAddressView(DomainFormBaseView):
|
||||||
"""Organization view"""
|
"""Organization view"""
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue