mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-15 17:17:02 +02:00
Merge remote-tracking branch 'origin/main' into rjm/811-approved-rejected-approved
This commit is contained in:
commit
f5bb77cef6
36 changed files with 1713 additions and 382 deletions
|
@ -1,4 +1,6 @@
|
|||
import logging
|
||||
from django import forms
|
||||
from django_fsm import get_available_FIELD_transitions
|
||||
from django.contrib import admin, messages
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
@ -6,10 +8,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 +119,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]
|
||||
|
@ -201,54 +227,123 @@ 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,
|
||||
"_delete_domain": self.do_delete_domain,
|
||||
"_get_status": self.do_get_status,
|
||||
}
|
||||
|
||||
# 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_delete_domain(self, request, obj):
|
||||
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(".")
|
||||
|
||||
def do_get_status(self, request, obj):
|
||||
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(".")
|
||||
|
||||
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.revert_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
|
||||
|
@ -271,6 +366,126 @@ 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 DomainApplicationAdminForm(forms.ModelForm):
|
||||
"""Custom form to limit transitions to available transitions"""
|
||||
|
||||
class Meta:
|
||||
model = models.DomainApplication
|
||||
fields = "__all__"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
application = kwargs.get("instance")
|
||||
if application and application.pk:
|
||||
current_state = application.status
|
||||
|
||||
# first option in status transitions is current state
|
||||
available_transitions = [(current_state, current_state)]
|
||||
|
||||
transitions = get_available_FIELD_transitions(
|
||||
application, models.DomainApplication._meta.get_field("status")
|
||||
)
|
||||
|
||||
for transition in transitions:
|
||||
available_transitions.append((transition.target, transition.target))
|
||||
|
||||
# only set the available transitions if the user is not restricted
|
||||
# from editing the domain application; otherwise, the form will be
|
||||
# readonly and the status field will not have a widget
|
||||
if not application.creator.is_restricted():
|
||||
self.fields["status"].widget.choices = available_transitions
|
||||
|
||||
|
||||
class DomainInformationAdmin(ListHeaderAdmin):
|
||||
|
@ -282,7 +497,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
|||
|
||||
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
|
||||
|
@ -311,6 +526,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
search_help_text = "Search by domain or submitter."
|
||||
|
||||
# Detail view
|
||||
form = DomainApplicationAdminForm
|
||||
fieldsets = [
|
||||
(None, {"fields": ["status", "investigator", "creator", "approved_domain"]}),
|
||||
(
|
||||
|
@ -324,8 +540,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
"federal_agency",
|
||||
"federal_type",
|
||||
"is_election_board",
|
||||
"type_of_work",
|
||||
"more_organization_information",
|
||||
"about_your_organization",
|
||||
]
|
||||
},
|
||||
),
|
||||
|
@ -363,8 +578,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
|||
# Read only that we'll leverage for CISA Analysts
|
||||
analyst_readonly_fields = [
|
||||
"creator",
|
||||
"type_of_work",
|
||||
"more_organization_information",
|
||||
"about_your_organization",
|
||||
"address_line1",
|
||||
"address_line2",
|
||||
"zipcode",
|
||||
|
@ -484,13 +698,17 @@ 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.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.PublicContact, AuditedAdmin)
|
||||
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
||||
admin.site.register(models.TransitionDomain, AuditedAdmin)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue