merge updates from main

This commit is contained in:
David Kennedy 2023-09-12 12:46:43 -04:00
commit 74fa04fd6c
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
13 changed files with 301 additions and 30 deletions

View file

@ -177,31 +177,59 @@ class DomainAdmin(ListHeaderAdmin):
readonly_fields = ["state"]
def response_change(self, request, obj):
PLACE_HOLD = "_place_client_hold"
EDIT_DOMAIN = "_edit_domain"
if PLACE_HOLD in request.POST:
try:
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(".")
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,)))
# Create dictionary of action functions
ACTION_FUNCTIONS = {
"_place_client_hold": self.do_place_client_hold,
"_remove_client_hold": self.do_remove_client_hold,
"_edit_domain": self.do_edit_domain,
}
# Check which action button was pressed and call the corresponding function
for action, function in ACTION_FUNCTIONS.items():
if action in request.POST:
return function(request, obj)
# If no matching action button is found, return the super method
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.remove_client_hold()
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):
# If the analyst was recently editing a domain page,
# delete any associated session values
@ -438,3 +466,4 @@ admin.site.register(models.Host, MyHostAdmin)
admin.site.register(models.Nameserver, MyHostAdmin)
admin.site.register(models.Website, AuditedAdmin)
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
admin.site.register(models.TransitionDomain, AuditedAdmin)

View file

@ -77,6 +77,11 @@ class UserFixture:
"first_name": "David",
"last_name": "Kennedy",
},
{
"username": "f14433d8-f0e9-41bf-9c72-b99b110e665d",
"first_name": "Nicolle",
"last_name": "LeClair",
},
]
STAFF = [
@ -123,6 +128,12 @@ class UserFixture:
"last_name": "DiSarli-Analyst",
"email": "gaby@truss.works",
},
{
"username": "cfe7c2fc-e24a-480e-8b78-28645a1459b3",
"first_name": "Nicolle-Analyst",
"last_name": "LeClair-Analyst",
"email": "nicolle.leclair@ecstech.com",
},
]
STAFF_PERMISSIONS = [

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 .user import User
from .website import Website
from .transition_domain import TransitionDomain
__all__ = [
"Contact",
@ -28,6 +29,7 @@ __all__ = [
"PublicContact",
"User",
"Website",
"TransitionDomain",
]
auditlog.register(Contact)
@ -42,3 +44,4 @@ auditlog.register(UserDomainRole)
auditlog.register(PublicContact)
auditlog.register(User)
auditlog.register(Website)
auditlog.register(TransitionDomain)

View file

@ -2,7 +2,7 @@ import logging
from datetime import date
from string import digits
from django_fsm import FSMField # type: ignore
from django_fsm import FSMField, transition # type: ignore
from django.db import models
@ -114,6 +114,12 @@ class Domain(TimeStampedModel, DomainHelper):
# 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):
"""
Python descriptor to turn class methods into properties.
@ -311,13 +317,17 @@ class Domain(TimeStampedModel, DomainHelper):
"""Time to renew. Not implemented."""
raise NotImplementedError()
@transition(field="state", source=[State.READY], target=State.ONHOLD)
def place_client_hold(self):
"""This domain should not be active."""
raise NotImplementedError("This is not implemented yet.")
# This method is changing the state of the domain in registrar
# TODO: implement EPP call
@transition(field="state", source=[State.ONHOLD], target=State.READY)
def remove_client_hold(self):
"""This domain is okay to be active."""
raise NotImplementedError()
# This method is changing the state of the domain in registrar
# TODO: implement EPP call
def __str__(self) -> str:
return self.name

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 %}
<main id="main-content" class="grid-container">
<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>
<div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"

View file

@ -8,8 +8,12 @@
{% block field_sets %}
<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 type="submit" value="Place hold" name="_place_client_hold">
</div>
{{ block.super }}
{% endblock %}

View file

@ -10,8 +10,7 @@
<h1>Authorizing official</h1>
<p>Your authorizing official is the person within your organization who can
authorize domain requests. This is generally the highest-ranking or
highest-elected official in your organization. Read more about <a class="usa-link" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
authorize domain requests. This person must be in a role of significant, executive responsibility within the organization. Read more about <a class="usa-link" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
{% include "includes/required_fields.html" %}

View file

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

View file

@ -11,11 +11,11 @@ from registrar.admin import (
AuditedAdmin,
)
from registrar.models import (
Domain,
DomainApplication,
DomainInformation,
User,
DomainInvitation,
Domain,
)
from .common import (
completed_application,
@ -23,11 +23,13 @@ from .common import (
mock_user,
create_superuser,
create_user,
create_ready_domain,
multiple_unalphabetical_domain_objects,
)
from django.contrib.sessions.backends.db import SessionStore
from django.contrib.auth import get_user_model
from unittest.mock import patch
from unittest import skip
from django.conf import settings
from unittest.mock import MagicMock
@ -37,6 +39,60 @@ import logging
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 TestDomainApplicationAdminForm(TestCase):
def setUp(self):
# Create a test application with an initial state of started