manage.get.gov/src/registrar/admin.py
2023-08-09 08:47:09 -06:00

336 lines
12 KiB
Python

import logging
from django.contrib import admin, messages
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponseRedirect
from django.urls import reverse
from . import models
logger = logging.getLogger(__name__)
class AuditedAdmin(admin.ModelAdmin):
"""Custom admin to make auditing easier."""
def history_view(self, request, object_id, extra_context=None):
"""On clicking 'History', take admin to the auditlog view for an object."""
return HttpResponseRedirect(
"{url}?resource_type={content_type}&object_id={object_id}".format(
url=reverse("admin:auditlog_logentry_changelist", args=()),
content_type=ContentType.objects.get_for_model(self.model).pk,
object_id=object_id,
)
)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Used to sort dropdown fields alphabetically but can be expanded upon"""
# Determines what we want to sort by, ex: by name
order_by_list = []
if db_field.name == "submitter" or db_field.name == "authorizing_official" or db_field.name == "creator":
order_by_list = ['first_name', 'last_name']
elif db_field.name == "requested_domain":
order_by_list = ['name']
return self.formfield_order_helper(order_by_list, db_field, request, **kwargs)
def formfield_order_helper(self, order_by_list, db_field, request, **kwargs):
"""A helper function to order a dropdown field in Django Admin, takes the fields you want to order by as an array"""
formfield = super(AuditedAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
# Only order if we choose to do so
if order_by_list:
formfield.queryset = formfield.queryset.order_by(*order_by_list)
return formfield
class ListHeaderAdmin(AuditedAdmin):
"""Custom admin to add a descriptive subheader to list views."""
def changelist_view(self, request, extra_context=None):
if extra_context is None:
extra_context = {}
# Get the filtered values
filters = self.get_filters(request)
# Pass the filtered values to the template context
extra_context["filters"] = filters
extra_context["search_query"] = request.GET.get(
"q", ""
) # Assuming the search query parameter is 'q'
return super().changelist_view(request, extra_context=extra_context)
def get_filters(self, request):
"""Retrieve the current set of parameters being used to filter the table
Returns:
dictionary objects in the format {parameter_name: string,
parameter_value: string}
TODO: convert investigator id to investigator username
"""
filters = []
# Retrieve the filter parameters
for param in request.GET.keys():
# Exclude the default search parameter 'q'
if param != "q" and param != "o":
parameter_name = (
param.replace("__exact", "")
.replace("_type", "")
.replace("__id", " id")
)
if parameter_name == "investigator id":
# Retrieves the corresponding contact from Users
id_value = request.GET.get(param)
try:
contact = models.User.objects.get(id=id_value)
investigator_name = contact.first_name + " " + contact.last_name
filters.append(
{
"parameter_name": "investigator",
"parameter_value": investigator_name,
}
)
except models.User.DoesNotExist:
pass
else:
# For other parameter names, append a dictionary with the original
# parameter_name and the corresponding parameter_value
filters.append(
{
"parameter_name": parameter_name,
"parameter_value": request.GET.get(param),
}
)
return filters
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]
def get_list_display(self, request):
if not request.user.is_superuser:
# Customize the list display for staff users
return ("email", "first_name", "last_name", "is_staff", "is_superuser")
# Use the default list display for non-staff users
return super().get_list_display(request)
def get_fieldsets(self, request, obj=None):
if not request.user.is_superuser:
# If the user doesn't have permission to change the model,
# show a read-only fieldset
return ((None, {"fields": []}),)
# If the user has permission to change the model, show all fields
return super().get_fieldsets(request, obj)
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."""
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):
ACTION_BUTTON = "_place_client_hold"
if ACTION_BUTTON 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(".")
return super().response_change(request, obj)
class ContactAdmin(ListHeaderAdmin):
"""Custom contact admin class to add search."""
search_fields = ["email", "first_name", "last_name"]
search_help_text = "Search by firstname, lastname or email."
class DomainApplicationAdmin(ListHeaderAdmin):
"""Customize the applications listing view."""
# Columns
list_display = [
"requested_domain",
"status",
"organization_type",
"created_at",
"submitter",
"investigator",
]
# Filters
list_filter = ("status", "organization_type", "investigator")
# Search
search_fields = [
"requested_domain__name",
"submitter__email",
"submitter__first_name",
"submitter__last_name",
]
search_help_text = "Search by domain or submitter."
# Detail view
fieldsets = [
(None, {"fields": ["status", "investigator", "creator"]}),
(
"Type of organization",
{
"fields": [
"organization_type",
"federally_recognized_tribe",
"state_recognized_tribe",
"tribe_name",
"federal_agency",
"federal_type",
"is_election_board",
"type_of_work",
"more_organization_information",
]
},
),
(
"Organization name and mailing address",
{
"fields": [
"organization_name",
"address_line1",
"address_line2",
"city",
"state_territory",
"zipcode",
"urbanization",
]
},
),
("Authorizing official", {"fields": ["authorizing_official"]}),
("Current websites", {"fields": ["current_websites"]}),
(".gov domain", {"fields": ["requested_domain", "alternative_domains"]}),
("Purpose of your domain", {"fields": ["purpose"]}),
("Your contact information", {"fields": ["submitter"]}),
("Other employees from your organization?", {"fields": ["other_contacts"]}),
(
"No other employees from your organization?",
{"fields": ["no_other_contacts_rationale"]},
),
("Anything else we should know?", {"fields": ["anything_else"]}),
(
"Requirements for operating .gov domains",
{"fields": ["is_policy_acknowledged"]},
),
]
# Read only that we'll leverage for CISA Analysts
readonly_fields = [
"creator",
"type_of_work",
"more_organization_information",
"address_line1",
"address_line2",
"zipcode",
"requested_domain",
"alternative_domains",
"purpose",
"submitter",
"no_other_contacts_rationale",
"anything_else",
"is_policy_acknowledged",
]
# Trigger action when a fieldset is changed
def save_model(self, request, obj, form, change):
if change: # Check if the application is being edited
# Get the original application from the database
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
if obj.status != original_obj.status:
if obj.status == models.DomainApplication.STARTED:
# No conditions
pass
elif obj.status == models.DomainApplication.SUBMITTED:
# This is an fsm in model which will throw an error if the
# transition condition is violated, so we call it on the
# original object which has the right status value, and pass
# the updated object which contains the up-to-date data
# for the side effects (like an email send). Same
# comment applies to original_obj method calls below.
original_obj.submit(updated_domain_application=obj)
elif obj.status == models.DomainApplication.IN_REVIEW:
original_obj.in_review(updated_domain_application=obj)
elif obj.status == models.DomainApplication.ACTION_NEEDED:
original_obj.action_needed(updated_domain_application=obj)
elif obj.status == models.DomainApplication.APPROVED:
original_obj.approve(updated_domain_application=obj)
elif obj.status == models.DomainApplication.WITHDRAWN:
original_obj.withdraw()
elif obj.status == models.DomainApplication.REJECTED:
original_obj.reject(updated_domain_application=obj)
else:
logger.warning("Unknown status selected in django admin")
super().save_model(request, obj, form, change)
def get_readonly_fields(self, request, obj=None):
if request.user.is_superuser:
# Superusers have full access, no fields are read-only
return []
else:
# Regular users can only view the specified fields
return self.readonly_fields
admin.site.register(models.User, MyUserAdmin)
admin.site.register(models.UserDomainRole, AuditedAdmin)
admin.site.register(models.Contact, ContactAdmin)
admin.site.register(models.DomainInvitation, AuditedAdmin)
admin.site.register(models.DomainInformation, AuditedAdmin)
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.DomainApplication, DomainApplicationAdmin)