Merge branch 'main' into ab/645-implement-test-cases-registry-integration

This commit is contained in:
zandercymatics 2023-09-12 15:45:28 -06:00
commit 4f45a90d15
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
12 changed files with 289 additions and 174 deletions

View file

@ -175,174 +175,62 @@ class DomainAdmin(ListHeaderAdmin):
change_form_template = "django/admin/domain_change_form.html" change_form_template = "django/admin/domain_change_form.html"
readonly_fields = ["state"] readonly_fields = ["state"]
def response_change(self, request, obj): # noqa def response_change(self, request, obj):
GET_SECURITY_EMAIL = "_get_security_email" # Create dictionary of action functions
SET_SECURITY_CONTACT = "_set_security_contact" ACTION_FUNCTIONS = {
MAKE_DOMAIN = "_make_domain_in_registry" "_place_client_hold": self.do_place_client_hold,
MAKE_NAMESERVERS = "_make_nameservers" "_remove_client_hold": self.do_remove_client_hold,
GET_NAMESERVERS = "_get_nameservers" "_edit_domain": self.do_edit_domain,
GET_STATUS = "_get_status" "_delete_domain": self.do_delete_domain,
SET_CLIENT_HOLD = "_set_client_hold" "_get_status": self.do_get_status
REMOVE_CLIENT_HOLD = "_rem_client_hold" }
DELETE_DOMAIN = "_delete_domain"
PLACE_HOLD = "_place_client_hold" # Check which action button was pressed and call the corresponding function
EDIT_DOMAIN = "_edit_domain" for action, function in ACTION_FUNCTIONS.items():
if PLACE_HOLD in request.POST: if action in request.POST:
try: return function(request, obj)
obj.place_client_hold()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
(
"%s is in client hold. This domain is no longer accessible on"
" the public internet."
)
% obj.name,
)
return HttpResponseRedirect(".")
if GET_SECURITY_EMAIL in request.POST: # If no matching action button is found, return the super method
try:
contacts = obj._get_property("contacts")
email = None
for contact in contacts:
if ["type", "email"] in contact.keys() and contact[
"type"
] == "security":
email = contact["email"]
if email is None:
raise ValueError(
"Security contact type is not available on this domain"
)
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("The security email is %s" ". Thanks!") % email,
)
return HttpResponseRedirect(".")
elif SET_SECURITY_CONTACT in request.POST:
try:
fake_email = "manuallyEnteredEmail@test.gov"
if PublicContact.objects.filter(
domain=obj, contact_type="security"
).exists():
sec_contact = PublicContact.objects.filter(
domain=obj, contact_type="security"
).get()
else:
sec_contact = obj.get_default_security_contact()
sec_contact.email = fake_email
sec_contact.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
"The security email is %s. Thanks!" % fake_email,
)
elif MAKE_DOMAIN in request.POST:
try:
obj._get_or_create_domain()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Domain created with %s" ". Thanks!") % obj.name,
)
return HttpResponseRedirect(".")
elif MAKE_NAMESERVERS in request.POST:
try:
hosts = [("ns1.example.com", None), ("ns2.example.com", None)]
obj.nameservers = hosts
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Hosts set to be %s" ". Thanks!") % hosts,
)
return HttpResponseRedirect(".")
elif GET_NAMESERVERS in request.POST:
try:
nameservers = obj.nameservers
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Nameservers are %s" ". Thanks!") % nameservers,
)
return HttpResponseRedirect(".")
elif GET_STATUS in request.POST:
try:
statuses = obj.statuses
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Domain statuses are %s" ". Thanks!") % statuses,
)
return HttpResponseRedirect(".")
elif SET_CLIENT_HOLD in request.POST:
try:
obj.clientHold()
obj.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Domain %s is now in clientHold") % obj.name,
)
return HttpResponseRedirect(".")
elif REMOVE_CLIENT_HOLD in request.POST:
try:
obj.revertClientHold()
obj.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Domain %s will now have client hold removed") % obj.name,
)
return HttpResponseRedirect(".")
elif DELETE_DOMAIN in request.POST:
try:
obj.deleted()
obj.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("Domain %s Should now be deleted " ". Thanks!") % obj.name,
)
return HttpResponseRedirect(".")
elif EDIT_DOMAIN in request.POST:
# We want to know, globally, when an edit action occurs
request.session["analyst_action"] = "edit"
# Restricts this action to this domain (pk) only
request.session["analyst_action_location"] = obj.id
return HttpResponseRedirect(reverse("domain", args=(obj.id,)))
return super().response_change(request, obj) return super().response_change(request, obj)
def do_place_client_hold(self, request, obj):
try:
obj.place_client_hold()
obj.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
(
"%s is in client hold. This domain is no longer accessible on"
" the public internet."
)
% obj.name,
)
return HttpResponseRedirect(".")
def do_remove_client_hold(self, request, obj):
try:
obj.revertClientHold()
obj.save()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
("%s is ready. This domain is accessible on the public internet.")
% obj.name,
)
return HttpResponseRedirect(".")
def do_edit_domain(self, request, obj):
# We want to know, globally, when an edit action occurs
request.session["analyst_action"] = "edit"
# Restricts this action to this domain (pk) only
request.session["analyst_action_location"] = obj.id
return HttpResponseRedirect(reverse("domain", args=(obj.id,)))
def change_view(self, request, object_id): def change_view(self, request, object_id):
# If the analyst was recently editing a domain page, # If the analyst was recently editing a domain page,
# delete any associated session values # delete any associated session values
@ -554,3 +442,4 @@ admin.site.register(models.Nameserver, MyHostAdmin)
admin.site.register(models.Website, AuditedAdmin) admin.site.register(models.Website, AuditedAdmin)
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)

View file

@ -131,7 +131,7 @@ class UserFixture:
{ {
"username": "cfe7c2fc-e24a-480e-8b78-28645a1459b3", "username": "cfe7c2fc-e24a-480e-8b78-28645a1459b3",
"first_name": "Nicolle-Analyst", "first_name": "Nicolle-Analyst",
"last_name": "LeClair", "last_name": "LeClair-Analyst",
"email": "nicolle.leclair@ecstech.com", "email": "nicolle.leclair@ecstech.com",
}, },
] ]

View file

@ -0,0 +1,30 @@
# Generated by Django 4.2.1 on 2023-09-07 17:53
from django.db import migrations
import django_fsm
class Migration(migrations.Migration):
dependencies = [
("registrar", "0030_alter_user_status"),
]
operations = [
migrations.AlterField(
model_name="domain",
name="state",
field=django_fsm.FSMField(
choices=[
("created", "Created"),
("deleted", "Deleted"),
("unknown", "Unknown"),
("ready", "Ready"),
("onhold", "Onhold"),
],
default="unknown",
help_text="Very basic info about the lifecycle of this domain object",
max_length=21,
protected=True,
),
),
]

View file

@ -0,0 +1,60 @@
# Generated by Django 4.2.1 on 2023-09-11 14:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0030_alter_user_status"),
]
operations = [
migrations.CreateModel(
name="TransitionDomain",
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)),
(
"username",
models.TextField(
help_text="Username - this will be an email address",
verbose_name="Username",
),
),
(
"domain_name",
models.TextField(blank=True, null=True, verbose_name="Domain name"),
),
(
"status",
models.CharField(
blank=True,
choices=[("created", "Created"), ("hold", "Hold")],
help_text="domain status during the transfer",
max_length=255,
verbose_name="Status",
),
),
(
"email_sent",
models.BooleanField(
default=False,
help_text="indicates whether email was sent",
verbose_name="email sent",
),
),
],
options={
"abstract": False,
},
),
]

View file

@ -0,0 +1,12 @@
# Generated by Django 4.2.1 on 2023-09-12 14:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("registrar", "0031_alter_domain_state"),
("registrar", "0031_transitiondomain"),
]
operations = []

View file

@ -13,6 +13,7 @@ from .user_domain_role import UserDomainRole
from .public_contact import PublicContact from .public_contact import PublicContact
from .user import User from .user import User
from .website import Website from .website import Website
from .transition_domain import TransitionDomain
__all__ = [ __all__ = [
"Contact", "Contact",
@ -28,6 +29,7 @@ __all__ = [
"PublicContact", "PublicContact",
"User", "User",
"Website", "Website",
"TransitionDomain",
] ]
auditlog.register(Contact) auditlog.register(Contact)
@ -42,3 +44,4 @@ auditlog.register(UserDomainRole)
auditlog.register(PublicContact) auditlog.register(PublicContact)
auditlog.register(User) auditlog.register(User)
auditlog.register(Website) auditlog.register(Website)
auditlog.register(TransitionDomain)

View file

@ -121,6 +121,15 @@ class Domain(TimeStampedModel, DomainHelper):
# previously existed but has been deleted from the registry # previously existed but has been deleted from the registry
DELETED = "deleted" DELETED = "deleted"
# the state is indeterminate
UNKNOWN = "unknown"
# the ready state for a domain object
READY = "ready"
# when a domain is on hold
ONHOLD = "onhold"
class Cache(property): class Cache(property):
""" """
Python descriptor to turn class methods into properties. Python descriptor to turn class methods into properties.
@ -639,12 +648,14 @@ class Domain(TimeStampedModel, DomainHelper):
def clientHoldStatus(self): def clientHoldStatus(self):
return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en") return epp.Status(state=self.Status.CLIENT_HOLD, description="", lang="en")
@transition(field="state", source=[State.READY], target=State.ONHOLD)
def _place_client_hold(self): def _place_client_hold(self):
"""This domain should not be active. """This domain should not be active.
may raises RegistryError, should be caught or handled correctly by caller""" may raises RegistryError, should be caught or handled correctly by caller"""
request = commands.UpdateDomain(name=self.name, add=[self.clientHoldStatus()]) request = commands.UpdateDomain(name=self.name, add=[self.clientHoldStatus()])
registry.send(request) registry.send(request)
@transition(field="state", source=[State.ONHOLD], target=State.READY)
def _remove_client_hold(self): def _remove_client_hold(self):
"""This domain is okay to be active. """This domain is okay to be active.
may raises RegistryError, should be caught or handled correctly by caller""" may raises RegistryError, should be caught or handled correctly by caller"""

View file

@ -0,0 +1,42 @@
from django.db import models
from .utility.time_stamped_model import TimeStampedModel
class TransitionDomain(TimeStampedModel):
"""Transition Domain model stores information about the
state of a domain upon transition between registry
providers"""
class StatusChoices(models.TextChoices):
CREATED = "created", "Created"
HOLD = "hold", "Hold"
username = models.TextField(
null=False,
blank=False,
verbose_name="Username",
help_text="Username - this will be an email address",
)
domain_name = models.TextField(
null=True,
blank=True,
verbose_name="Domain name",
)
status = models.CharField(
max_length=255,
null=False,
blank=True,
choices=StatusChoices.choices,
verbose_name="Status",
help_text="domain status during the transfer",
)
email_sent = models.BooleanField(
null=False,
default=False,
verbose_name="email sent",
help_text="indicates whether email was sent",
)
def __str__(self):
return self.username

View file

@ -6,6 +6,15 @@
{% block content %} {% block content %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
<a href="{% url 'home' %}" class="breadcrumb__back">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
</svg>
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
Back to manage your domains
</p>
</a>
<h1>Domain request for {{ domainapplication.requested_domain.name }}</h1> <h1>Domain request for {{ domainapplication.requested_domain.name }}</h1>
<div <div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2" class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"

View file

@ -8,16 +8,13 @@
{% block field_sets %} {% block field_sets %}
<div class="submit-row"> <div class="submit-row">
{% if original.state == original.State.READY %}
<input type="submit" value="Place hold" name="_place_client_hold">
{% elif original.state == original.State.ONHOLD %}
<input type="submit" value="Remove hold" name="_remove_client_hold">
{% endif %}
<input id="manageDomainSubmitButton" type="submit" value="Manage Domain" name="_edit_domain"> <input id="manageDomainSubmitButton" type="submit" value="Manage Domain" name="_edit_domain">
<input type="submit" value="Place hold" name="_place_client_hold">
<input type="submit" value="Get the email" name="_get_security_email">
<input type="submit" value="Set New Security Contact" name="_set_security_contact">
<input type="submit" value="Create the domain obj" name="_make_domain_in_registry">
<input type="submit" value="add nameservers" name="_make_nameservers">
<input type="submit" value="get nameservers" name="_get_nameservers">
<input type="submit" value="get status" name="_get_status"> <input type="submit" value="get status" name="_get_status">
<input type="submit" value="Actual Client Hold Set" name="_set_client_hold">
<input type="submit" value="remove Client Hold" name="_rem_client_hold">
<input type="submit" value="EPP Delete Domain" name="_delete_domain"> <input type="submit" value="EPP Delete Domain" name="_delete_domain">
</div> </div>
{{ block.super }} {{ block.super }}

View file

@ -430,10 +430,16 @@ def create_user():
return User.objects.create_user( return User.objects.create_user(
username="staffuser", username="staffuser",
email="user@example.com", email="user@example.com",
is_staff=True,
password=p, password=p,
) )
def create_ready_domain():
domain, _ = Domain.objects.get_or_create(name="city.gov", state=Domain.State.READY)
return domain
def completed_application( def completed_application(
has_other_contacts=True, has_other_contacts=True,
has_current_website=True, has_current_website=True,

View file

@ -10,11 +10,11 @@ from registrar.admin import (
AuditedAdmin, AuditedAdmin,
) )
from registrar.models import ( from registrar.models import (
Domain,
DomainApplication, DomainApplication,
DomainInformation, DomainInformation,
User, User,
DomainInvitation, DomainInvitation,
Domain,
) )
from .common import ( from .common import (
completed_application, completed_application,
@ -22,11 +22,13 @@ from .common import (
mock_user, mock_user,
create_superuser, create_superuser,
create_user, create_user,
create_ready_domain,
multiple_unalphabetical_domain_objects, multiple_unalphabetical_domain_objects,
) )
from django.contrib.sessions.backends.db import SessionStore from django.contrib.sessions.backends.db import SessionStore
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from unittest.mock import patch from unittest.mock import patch
from unittest import skip
from django.conf import settings from django.conf import settings
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -36,6 +38,60 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class TestDomainAdmin(TestCase):
def setUp(self):
self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser()
self.staffuser = create_user()
def test_place_and_remove_hold(self):
domain = create_ready_domain()
# get admin page and assert Place Hold button
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/domain/{}/change/".format(domain.pk),
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Place hold")
self.assertNotContains(response, "Remove hold")
# submit place_client_hold and assert Remove Hold button
response = self.client.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_place_client_hold": "Place hold", "name": domain.name},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Remove hold")
self.assertNotContains(response, "Place hold")
# submit remove client hold and assert Place hold button
response = self.client.post(
"/admin/registrar/domain/{}/change/".format(domain.pk),
{"_remove_client_hold": "Remove hold", "name": domain.name},
follow=True,
)
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain.name)
self.assertContains(response, "Place hold")
self.assertNotContains(response, "Remove hold")
@skip("Waiting on epp lib to implement")
def test_place_and_remove_hold_epp(self):
raise
def tearDown(self):
Domain.objects.all().delete()
User.objects.all().delete()
class TestDomainApplicationAdmin(TestCase): class TestDomainApplicationAdmin(TestCase):
def setUp(self): def setUp(self):
self.site = AdminSite() self.site = AdminSite()