mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-03 08:22:18 +02:00
Merge branch 'main' into za/850-epp-contact-get
This commit is contained in:
commit
80d37777c0
17 changed files with 508 additions and 51 deletions
|
@ -6,10 +6,36 @@ from django.http.response import HttpResponseRedirect
|
|||
from django.urls import reverse
|
||||
from registrar.models.utility.admin_sort_fields import AdminSortFields
|
||||
from . import models
|
||||
from auditlog.models import LogEntry # type: ignore
|
||||
from auditlog.admin import LogEntryAdmin # type: ignore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomLogEntryAdmin(LogEntryAdmin):
|
||||
"""Overwrite the generated LogEntry admin class"""
|
||||
|
||||
list_display = [
|
||||
"created",
|
||||
"resource",
|
||||
"action",
|
||||
"msg_short",
|
||||
"user_url",
|
||||
]
|
||||
|
||||
# We name the custom prop 'resource' because linter
|
||||
# is not allowing a short_description attr on it
|
||||
# This gets around the linter limitation, for now.
|
||||
def resource(self, obj):
|
||||
# Return the field value without a link
|
||||
return f"{obj.content_type} - {obj.object_repr}"
|
||||
|
||||
search_help_text = "Search by resource, changes, or user."
|
||||
|
||||
change_form_template = "admin/change_form_no_submit.html"
|
||||
add_form_template = "admin/change_form_no_submit.html"
|
||||
|
||||
|
||||
class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
|
||||
"""Custom admin to make auditing easier."""
|
||||
|
||||
|
@ -91,14 +117,12 @@ class ListHeaderAdmin(AuditedAdmin):
|
|||
|
||||
|
||||
class UserContactInline(admin.StackedInline):
|
||||
|
||||
"""Edit a user's profile on the user page."""
|
||||
|
||||
model = models.Contact
|
||||
|
||||
|
||||
class MyUserAdmin(BaseUserAdmin):
|
||||
|
||||
"""Custom user admin class to use our inlines."""
|
||||
|
||||
inlines = [UserContactInline]
|
||||
|
@ -152,54 +176,96 @@ class MyUserAdmin(BaseUserAdmin):
|
|||
|
||||
|
||||
class HostIPInline(admin.StackedInline):
|
||||
|
||||
"""Edit an ip address on the host page."""
|
||||
|
||||
model = models.HostIP
|
||||
|
||||
|
||||
class MyHostAdmin(AuditedAdmin):
|
||||
|
||||
"""Custom host admin class to use our inlines."""
|
||||
|
||||
inlines = [HostIPInline]
|
||||
|
||||
|
||||
class DomainAdmin(ListHeaderAdmin):
|
||||
|
||||
"""Custom domain admin class to add extra buttons."""
|
||||
|
||||
# Columns
|
||||
list_display = [
|
||||
"name",
|
||||
"organization_type",
|
||||
"state",
|
||||
]
|
||||
|
||||
def organization_type(self, obj):
|
||||
return obj.domain_info.organization_type
|
||||
|
||||
organization_type.admin_order_field = ( # type: ignore
|
||||
"domain_info__organization_type"
|
||||
)
|
||||
|
||||
# Filters
|
||||
list_filter = ["domain_info__organization_type"]
|
||||
|
||||
search_fields = ["name"]
|
||||
search_help_text = "Search by domain name."
|
||||
change_form_template = "django/admin/domain_change_form.html"
|
||||
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
|
||||
|
@ -222,11 +288,100 @@ class ContactAdmin(ListHeaderAdmin):
|
|||
|
||||
search_fields = ["email", "first_name", "last_name"]
|
||||
search_help_text = "Search by firstname, lastname or email."
|
||||
list_display = [
|
||||
"contact",
|
||||
"email",
|
||||
]
|
||||
|
||||
# We name the custom prop 'contact' because linter
|
||||
# is not allowing a short_description attr on it
|
||||
# This gets around the linter limitation, for now.
|
||||
def contact(self, obj: models.Contact):
|
||||
"""Duplicate the contact _str_"""
|
||||
if obj.first_name or obj.last_name:
|
||||
return obj.get_formatted_name()
|
||||
elif obj.email:
|
||||
return obj.email
|
||||
elif obj.pk:
|
||||
return str(obj.pk)
|
||||
else:
|
||||
return ""
|
||||
|
||||
contact.admin_order_field = "first_name" # type: ignore
|
||||
|
||||
|
||||
class WebsiteAdmin(ListHeaderAdmin):
|
||||
"""Custom website admin class."""
|
||||
|
||||
# Search
|
||||
search_fields = [
|
||||
"website",
|
||||
]
|
||||
search_help_text = "Search by website."
|
||||
|
||||
|
||||
class UserDomainRoleAdmin(ListHeaderAdmin):
|
||||
"""Custom domain role admin class."""
|
||||
|
||||
# Columns
|
||||
list_display = [
|
||||
"user",
|
||||
"domain",
|
||||
"role",
|
||||
]
|
||||
|
||||
# Search
|
||||
search_fields = [
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"domain__name",
|
||||
"role",
|
||||
]
|
||||
search_help_text = "Search by user, domain, or role."
|
||||
|
||||
|
||||
class DomainInvitationAdmin(ListHeaderAdmin):
|
||||
"""Custom domain invitation admin class."""
|
||||
|
||||
# Columns
|
||||
list_display = [
|
||||
"email",
|
||||
"domain",
|
||||
"status",
|
||||
]
|
||||
|
||||
# Search
|
||||
search_fields = [
|
||||
"email",
|
||||
"domain__name",
|
||||
]
|
||||
search_help_text = "Search by email or domain."
|
||||
|
||||
|
||||
class DomainInformationAdmin(ListHeaderAdmin):
|
||||
"""Customize domain information admin class."""
|
||||
|
||||
# Columns
|
||||
list_display = [
|
||||
"domain",
|
||||
"organization_type",
|
||||
"created_at",
|
||||
"submitter",
|
||||
]
|
||||
|
||||
# Filters
|
||||
list_filter = ["organization_type"]
|
||||
|
||||
# Search
|
||||
search_fields = [
|
||||
"domain__name",
|
||||
]
|
||||
search_help_text = "Search by domain."
|
||||
|
||||
|
||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||
|
||||
"""Customize the applications listing view."""
|
||||
"""Custom domain applications admin class."""
|
||||
|
||||
# Set multi-selects 'read-only' (hide selects and show data)
|
||||
# based on user perms and application creator's status
|
||||
|
@ -400,13 +555,16 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
return super().change_view(request, object_id, form_url, extra_context)
|
||||
|
||||
|
||||
admin.site.unregister(LogEntry) # Unregister the default registration
|
||||
admin.site.register(LogEntry, CustomLogEntryAdmin)
|
||||
admin.site.register(models.User, MyUserAdmin)
|
||||
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
||||
admin.site.register(models.UserDomainRole, UserDomainRoleAdmin)
|
||||
admin.site.register(models.Contact, ContactAdmin)
|
||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
||||
admin.site.register(models.DomainInvitation, DomainInvitationAdmin)
|
||||
admin.site.register(models.DomainInformation, DomainInformationAdmin)
|
||||
admin.site.register(models.Domain, DomainAdmin)
|
||||
admin.site.register(models.Host, MyHostAdmin)
|
||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||
admin.site.register(models.Website, AuditedAdmin)
|
||||
admin.site.register(models.Website, WebsiteAdmin)
|
||||
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
||||
admin.site.register(models.TransitionDomain, AuditedAdmin)
|
||||
|
|
|
@ -31,7 +31,7 @@ html[data-theme="light"] {
|
|||
|
||||
// #{$theme-link-color} would interpolate to 'primary', so we use the source value instead
|
||||
--link-fg: #{$theme-color-primary};
|
||||
--link-hover-color: #{$theme-color-primary-darker};
|
||||
--link-hover-color: #{$theme-color-primary};
|
||||
// $theme-link-visited-color - violet-70v
|
||||
--link-selected-fg: #54278f;
|
||||
|
||||
|
@ -140,11 +140,6 @@ h1, h2, h3 {
|
|||
font-weight: font-weight('bold');
|
||||
}
|
||||
|
||||
table > caption > a {
|
||||
font-weight: font-weight('bold');
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.change-list {
|
||||
.usa-table--striped tbody tr:nth-child(odd) td,
|
||||
.usa-table--striped tbody tr:nth-child(odd) th,
|
||||
|
@ -158,9 +153,12 @@ table > caption > a {
|
|||
padding-top: 20px;
|
||||
}
|
||||
|
||||
// 'Delete button' layout bug
|
||||
.submit-row a.deletelink {
|
||||
// Fix django admin button height bugs
|
||||
.submit-row a.deletelink,
|
||||
.delete-confirmation form .cancel-link,
|
||||
.submit-row a.closelink {
|
||||
height: auto!important;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// Keep th from collapsing
|
||||
|
@ -170,3 +168,15 @@ table > caption > a {
|
|||
.min-width-81 {
|
||||
min-width: 81px;
|
||||
}
|
||||
|
||||
.primary-th {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: none;
|
||||
font-weight: font-weight('bold');
|
||||
text-align: left;
|
||||
background: var(--primary);
|
||||
color: var(--header-link-color);
|
||||
}
|
|
@ -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 = [
|
||||
|
|
30
src/registrar/migrations/0031_alter_domain_state.py
Normal file
30
src/registrar/migrations/0031_alter_domain_state.py
Normal 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,
|
||||
),
|
||||
),
|
||||
]
|
60
src/registrar/migrations/0031_transitiondomain.py
Normal file
60
src/registrar/migrations/0031_transitiondomain.py
Normal 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,
|
||||
},
|
||||
),
|
||||
]
|
|
@ -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 = []
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
42
src/registrar/models/transition_domain.py
Normal file
42
src/registrar/models/transition_domain.py
Normal 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
|
|
@ -4,12 +4,20 @@
|
|||
{% for app in app_list %}
|
||||
<div class="app-{{ app.app_label }} module{% if app.app_url in request.path|urlencode %} current-app{% endif %}">
|
||||
<table>
|
||||
<caption>
|
||||
<a href="{{ app.app_url }}" class="section" title="{% blocktranslate with name=app.name %}Models in the {{ name }} application{% endblocktranslate %}">{{ app.name }}</a>
|
||||
</caption>
|
||||
|
||||
{# .gov override #}
|
||||
{# .gov override: add headers #}
|
||||
<thead>
|
||||
<tr>
|
||||
{% if show_changelinks %}
|
||||
<th colspan="3" class="primary-th">
|
||||
{{ app.name }}
|
||||
</th>
|
||||
{% else %}
|
||||
<th colspan="2" class="primary-th">
|
||||
{{ app.name }}
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">Model</th>
|
||||
<th><span class="display-inline-block min-width-25">Add</span></th>
|
||||
|
|
|
@ -2,6 +2,24 @@
|
|||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block extrahead %}
|
||||
<link rel="icon" type="image/png" sizes="32x32"
|
||||
href="{% static 'img/registrar/favicons/favicon-32.png' %}"
|
||||
>
|
||||
<link rel="icon" type="image/png" sizes="192x192"
|
||||
href="{% static 'img/registrar/favicons/favicon-192.png' %}"
|
||||
>
|
||||
<link rel="icon" type="image/svg+xml"
|
||||
href="{% static 'img/registrar/favicons/favicon.svg' %}"
|
||||
>
|
||||
<link rel="shortcut icon" type="image/x-icon"
|
||||
href="{% static 'img/registrar/favicons/favicon.ico' %}"
|
||||
>
|
||||
<link rel="apple-touch-icon" size="180x180"
|
||||
href="{% static 'img/registrar/favicons/favicon-180.png' %}"
|
||||
>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}
|
||||
|
|
|
@ -9,4 +9,4 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
20
src/registrar/templates/admin/change_form_no_submit.html
Normal file
20
src/registrar/templates/admin/change_form_no_submit.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% comment %} Replace the Django ul markup with a div. We'll edit the child markup accordingly in change_form_object_tools {% endcomment %}
|
||||
{% block object-tools %}
|
||||
{% if change and not is_popup %}
|
||||
<div class="object-tools">
|
||||
{% block object-tools-items %}
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block submit_buttons_top %}
|
||||
{# Do not render the submit buttons #}
|
||||
{% endblock %}
|
||||
|
||||
{% block submit_buttons_bottom %}
|
||||
{# Do not render the submit buttons #}
|
||||
{% endblock %}
|
|
@ -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"
|
||||
|
|
|
@ -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 %}
|
|
@ -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,
|
||||
|
|
|
@ -10,11 +10,11 @@ from registrar.admin import (
|
|||
AuditedAdmin,
|
||||
)
|
||||
from registrar.models import (
|
||||
Domain,
|
||||
DomainApplication,
|
||||
DomainInformation,
|
||||
User,
|
||||
DomainInvitation,
|
||||
Domain,
|
||||
)
|
||||
from .common import (
|
||||
completed_application,
|
||||
|
@ -22,11 +22,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
|
||||
|
@ -36,6 +38,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 TestDomainApplicationAdmin(TestCase):
|
||||
def setUp(self):
|
||||
self.site = AdminSite()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue