mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-30 22:46:30 +02:00
Merge branch 'main' into dk/857-epp-client-hold
This commit is contained in:
commit
15689e53cc
5 changed files with 253 additions and 1 deletions
|
@ -639,6 +639,21 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
class TransitionDomainAdmin(ListHeaderAdmin):
|
||||||
|
"""Custom transition domain admin class."""
|
||||||
|
|
||||||
|
# Columns
|
||||||
|
list_display = [
|
||||||
|
"username",
|
||||||
|
"domain_name",
|
||||||
|
"status",
|
||||||
|
"email_sent",
|
||||||
|
]
|
||||||
|
|
||||||
|
search_fields = ["username", "domain_name"]
|
||||||
|
search_help_text = "Search by user or domain name."
|
||||||
|
|
||||||
|
|
||||||
class DomainInformationInline(admin.StackedInline):
|
class DomainInformationInline(admin.StackedInline):
|
||||||
"""Edit a domain information on the domain page.
|
"""Edit a domain information on the domain page.
|
||||||
We had issues inheriting from both StackedInline
|
We had issues inheriting from both StackedInline
|
||||||
|
@ -819,4 +834,4 @@ admin.site.register(models.Nameserver, MyHostAdmin)
|
||||||
admin.site.register(models.Website, WebsiteAdmin)
|
admin.site.register(models.Website, WebsiteAdmin)
|
||||||
admin.site.register(models.PublicContact, AuditedAdmin)
|
admin.site.register(models.PublicContact, AuditedAdmin)
|
||||||
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
||||||
admin.site.register(models.TransitionDomain, AuditedAdmin)
|
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
"""Data migration: Generate fake transition domains, replacing existing ones."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from registrar.models import TransitionDomain, Domain
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Generate test transition domains from existing domains"
|
||||||
|
|
||||||
|
# Generates test transition domains for testing send_domain_invitations script.
|
||||||
|
# Running this script removes all existing transition domains, so use with caution.
|
||||||
|
# Transition domains are created with email addresses provided as command line
|
||||||
|
# argument. Email addresses for testing are passed as comma delimited list of
|
||||||
|
# email addresses, and are required to be provided. Email addresses from the list
|
||||||
|
# are assigned to transition domains at time of creation.
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
"""Add command line arguments."""
|
||||||
|
parser.add_argument(
|
||||||
|
"-e",
|
||||||
|
"--emails",
|
||||||
|
required=True,
|
||||||
|
dest="emails",
|
||||||
|
help="Comma-delimited list of email addresses to be used for testing",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, **options):
|
||||||
|
"""Delete existing TransitionDomains. Generate test ones.
|
||||||
|
expects options[emails]; emails will be assigned to transition
|
||||||
|
domains at the time of creation"""
|
||||||
|
|
||||||
|
# split options[emails] into an array of test emails
|
||||||
|
test_emails = options["emails"].split(",")
|
||||||
|
|
||||||
|
if len(test_emails) > 0:
|
||||||
|
# set up test data
|
||||||
|
self.delete_test_transition_domains()
|
||||||
|
self.load_test_transition_domains(test_emails)
|
||||||
|
else:
|
||||||
|
logger.error("list of emails for testing is required")
|
||||||
|
|
||||||
|
def load_test_transition_domains(self, test_emails: list):
|
||||||
|
"""Load test transition domains"""
|
||||||
|
|
||||||
|
# counter for test_emails index
|
||||||
|
test_emails_counter = 0
|
||||||
|
# Need to get actual domain names from the database for this test
|
||||||
|
real_domains = Domain.objects.all()
|
||||||
|
for real_domain in real_domains:
|
||||||
|
TransitionDomain.objects.create(
|
||||||
|
username=test_emails[test_emails_counter % len(test_emails)],
|
||||||
|
domain_name=real_domain.name,
|
||||||
|
status="created",
|
||||||
|
email_sent=False,
|
||||||
|
)
|
||||||
|
test_emails_counter += 1
|
||||||
|
|
||||||
|
def delete_test_transition_domains(self):
|
||||||
|
self.transition_domains = TransitionDomain.objects.all()
|
||||||
|
for transition_domain in self.transition_domains:
|
||||||
|
transition_domain.delete()
|
142
src/registrar/management/commands/send_domain_invitations.py
Normal file
142
src/registrar/management/commands/send_domain_invitations.py
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
"""Data migration: Send domain invitations once to existing customers."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from registrar.models import TransitionDomain
|
||||||
|
from ...utility.email import send_templated_email, EmailSendingError
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Send domain invitations once to existing customers."
|
||||||
|
|
||||||
|
# this array is used to store and process the transition_domains
|
||||||
|
transition_domains: List[str] = []
|
||||||
|
# this array is used to store domains with errors, which are not
|
||||||
|
# sent emails; this array is used to update the succesful
|
||||||
|
# transition_domains to email_sent=True, and also to report
|
||||||
|
# out errors
|
||||||
|
domains_with_errors: List[str] = []
|
||||||
|
# this array is used to store email_context; each item in the array
|
||||||
|
# contains the context for a single email; single emails may be 1
|
||||||
|
# or more transition_domains, as they are grouped by username
|
||||||
|
emails_to_send: List[str] = []
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
"""Add command line arguments."""
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--send_emails",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
dest="send_emails",
|
||||||
|
help="Send emails ",
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, **options):
|
||||||
|
"""Process the objects in TransitionDomain."""
|
||||||
|
|
||||||
|
logger.info("checking domains and preparing emails")
|
||||||
|
# Get all TransitionDomain objects
|
||||||
|
self.transition_domains = TransitionDomain.objects.filter(
|
||||||
|
email_sent=False,
|
||||||
|
).order_by("username")
|
||||||
|
|
||||||
|
self.build_emails_to_send_array()
|
||||||
|
|
||||||
|
if options["send_emails"]:
|
||||||
|
logger.info("about to send emails")
|
||||||
|
self.send_emails()
|
||||||
|
logger.info("done sending emails")
|
||||||
|
|
||||||
|
self.update_domains_as_sent()
|
||||||
|
|
||||||
|
logger.info("done sending emails and updating transition_domains")
|
||||||
|
else:
|
||||||
|
logger.info("not sending emails")
|
||||||
|
|
||||||
|
def build_emails_to_send_array(self):
|
||||||
|
"""this method sends emails to distinct usernames"""
|
||||||
|
|
||||||
|
# data structure to hold email context for a single email;
|
||||||
|
# transition_domains ordered by username, a single email_context
|
||||||
|
# may include information from more than one transition_domain
|
||||||
|
email_context = {"email": ""}
|
||||||
|
|
||||||
|
# loop through all transition_domains; group them by username
|
||||||
|
# into emails_to_send_array
|
||||||
|
for transition_domain in self.transition_domains:
|
||||||
|
# attempt to get the domain from domain objects; if there is
|
||||||
|
# an error getting the domain, skip this domain and add it to
|
||||||
|
# domains_with_errors
|
||||||
|
try:
|
||||||
|
# if prior username does not match current username
|
||||||
|
if (
|
||||||
|
not email_context["email"]
|
||||||
|
or email_context["email"] != transition_domain.username
|
||||||
|
):
|
||||||
|
# if not first in list of transition_domains
|
||||||
|
if email_context["email"]:
|
||||||
|
# append the email context to the emails_to_send array
|
||||||
|
self.emails_to_send.append(copy.deepcopy(email_context))
|
||||||
|
email_context["domains"] = []
|
||||||
|
email_context["email"] = transition_domain.username
|
||||||
|
email_context["domains"].append(transition_domain.domain_name)
|
||||||
|
except Exception as err:
|
||||||
|
# error condition if domain not in database
|
||||||
|
self.domains_with_errors.append(
|
||||||
|
copy.deepcopy(transition_domain.domain_name)
|
||||||
|
)
|
||||||
|
logger.error(
|
||||||
|
f"error retrieving domain {transition_domain.domain_name}: {err}"
|
||||||
|
)
|
||||||
|
# if there are at least one more transition domains than errors,
|
||||||
|
# then append one more item
|
||||||
|
if len(self.transition_domains) > len(self.domains_with_errors):
|
||||||
|
self.emails_to_send.append(email_context)
|
||||||
|
|
||||||
|
def send_emails(self):
|
||||||
|
if len(self.emails_to_send) > 0:
|
||||||
|
for email_data in self.emails_to_send:
|
||||||
|
self.send_email(email_data)
|
||||||
|
else:
|
||||||
|
logger.info("no emails to send")
|
||||||
|
|
||||||
|
def send_email(self, email_data):
|
||||||
|
try:
|
||||||
|
send_templated_email(
|
||||||
|
"emails/transition_domain_invitation.txt",
|
||||||
|
"emails/transition_domain_invitation_subject.txt",
|
||||||
|
to_address=email_data["email"],
|
||||||
|
context={
|
||||||
|
"domains": email_data["domains"],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# success message is logged
|
||||||
|
logger.info(
|
||||||
|
f"email sent successfully to {email_data['email']} for "
|
||||||
|
f"{[domain for domain in email_data['domains']]}"
|
||||||
|
)
|
||||||
|
except EmailSendingError as err:
|
||||||
|
logger.error(
|
||||||
|
f"email did not send successfully to {email_data['email']} "
|
||||||
|
f"for {[domain for domain in email_data['domains']]}"
|
||||||
|
f": {err}"
|
||||||
|
)
|
||||||
|
# if email failed to send, set error in domains_with_errors for each
|
||||||
|
# domain in the email so that transition domain email_sent is not set
|
||||||
|
# to True
|
||||||
|
for domain in email_data["domains"]:
|
||||||
|
self.domains_with_errors.append(domain)
|
||||||
|
|
||||||
|
def update_domains_as_sent(self):
|
||||||
|
"""set email_sent to True in all transition_domains which have
|
||||||
|
been processed successfully"""
|
||||||
|
for transition_domain in self.transition_domains:
|
||||||
|
if transition_domain.domain_name not in self.domains_with_errors:
|
||||||
|
transition_domain.email_sent = True
|
||||||
|
transition_domain.save()
|
|
@ -0,0 +1,29 @@
|
||||||
|
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||||
|
Hi.
|
||||||
|
|
||||||
|
You have been added as a manager on {% if domains|length > 1 %}multiple domains (listed below){% else %}{{ domains.0 }}{% endif %}.
|
||||||
|
|
||||||
|
YOU NEED A LOGIN.GOV ACCOUNT
|
||||||
|
You’ll need a Login.gov account to manage your .gov domain{% if domains|length > 1 %}s{% endif %}. Login.gov provides a simple and secure process for signing into many government services with one account. If you don’t already have one, follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||||
|
|
||||||
|
DOMAIN MANAGEMENT
|
||||||
|
As a .gov domain manager you can add or update information about your domain{% if domains|length > 1 %}s{% endif %}. You’ll also serve as a contact for your .gov domain{% if domains|length > 1 %}s{% endif %}. Please keep your contact information updated. Learn more about domain management <https://get.gov/help/>.
|
||||||
|
{% if domains|length > 1 %}
|
||||||
|
DOMAINS
|
||||||
|
{% for domain in domains %} {{ domain }}
|
||||||
|
{% endfor %}{% else %}
|
||||||
|
{% endif %}
|
||||||
|
SOMETHING WRONG?
|
||||||
|
If you’re not affiliated with {{ domain }} or think you received this message in error, contact the .gov team <https://get.gov/help/#contact-us>.
|
||||||
|
|
||||||
|
|
||||||
|
THANK YOU
|
||||||
|
|
||||||
|
.Gov helps the public identify official, trusted information. Thank you for using a .gov domain.
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
The .gov team
|
||||||
|
Contact us: <https://get.gov/contact/>
|
||||||
|
Visit <https://get.gov>
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1 @@
|
||||||
|
You've been added to a .gov domain
|
Loading…
Add table
Add a link
Reference in a new issue