mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-25 03:58:39 +02:00
Improve performance
This commit is contained in:
parent
7ec4b32f88
commit
a51949da0b
1 changed files with 141 additions and 90 deletions
|
@ -1,6 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models.functions import Concat
|
from django.db.models.functions import Concat, Coalesce
|
||||||
|
from django.db.models import Value, CharField
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django_fsm import get_available_FIELD_transitions
|
from django_fsm import get_available_FIELD_transitions
|
||||||
|
@ -12,6 +14,7 @@ from django.http.response import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from epplibwrapper.errors import ErrorCode, RegistryError
|
from epplibwrapper.errors import ErrorCode, RegistryError
|
||||||
from registrar.models.domain import Domain
|
from registrar.models.domain import Domain
|
||||||
|
from registrar.models.domain_application import DomainApplication
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
from registrar.utility import csv_export
|
from registrar.utility import csv_export
|
||||||
from registrar.views.utility.mixins import OrderableFieldsMixin
|
from registrar.views.utility.mixins import OrderableFieldsMixin
|
||||||
|
@ -707,6 +710,17 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
return readonly_fields # Read-only fields for analysts
|
return readonly_fields # Read-only fields for analysts
|
||||||
|
|
||||||
|
|
||||||
|
class Timer:
|
||||||
|
def __enter__(self):
|
||||||
|
self.start = time.time()
|
||||||
|
return self # This allows usage of the instance within the with block
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.end = time.time()
|
||||||
|
self.duration = self.end - self.start
|
||||||
|
logger.info(f"Execution time: {self.duration} seconds")
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdminForm(forms.ModelForm):
|
class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
"""Custom form to limit transitions to available transitions"""
|
"""Custom form to limit transitions to available transitions"""
|
||||||
|
|
||||||
|
@ -753,15 +767,41 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
"""Lookup reimplementation, gets users of is_staff.
|
"""Lookup reimplementation, gets users of is_staff.
|
||||||
Returns a list of tuples consisting of (user.id, user)
|
Returns a list of tuples consisting of (user.id, user)
|
||||||
"""
|
"""
|
||||||
privileged_users = User.objects.filter(is_staff=True).order_by("first_name", "last_name", "email")
|
logger.info("timing lookups")
|
||||||
return [(user.id, user) for user in privileged_users]
|
with Timer() as t:
|
||||||
|
|
||||||
|
# Select all investigators that are staff, then order by name and email
|
||||||
|
privileged_users = (
|
||||||
|
DomainApplication.objects.select_related("investigator")
|
||||||
|
.filter(investigator__is_staff=True)
|
||||||
|
.order_by(
|
||||||
|
"investigator__first_name",
|
||||||
|
"investigator__last_name",
|
||||||
|
"investigator__email"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Annotate the full name and return a values list that lookups can use
|
||||||
|
privileged_users_annotated = privileged_users.annotate(
|
||||||
|
full_name=Coalesce(
|
||||||
|
Concat(
|
||||||
|
"investigator__first_name", Value(" "), "investigator__last_name", output_field=CharField()
|
||||||
|
),
|
||||||
|
"investigator__email",
|
||||||
|
output_field=CharField()
|
||||||
|
)
|
||||||
|
).values_list("investigator__id", "full_name")
|
||||||
|
|
||||||
|
return privileged_users_annotated
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
"""Custom queryset implementation, filters by investigator"""
|
"""Custom queryset implementation, filters by investigator"""
|
||||||
if self.value() is None:
|
logger.info("timing queryset")
|
||||||
return queryset
|
with Timer() as t:
|
||||||
else:
|
if self.value() is None:
|
||||||
return queryset.filter(investigator__id__exact=self.value())
|
return queryset
|
||||||
|
else:
|
||||||
|
return queryset.filter(investigator__id__exact=self.value())
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
|
@ -863,77 +903,83 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
# lists in filter_horizontal are not sorted properly, sort them
|
# lists in filter_horizontal are not sorted properly, sort them
|
||||||
# by website
|
# by website
|
||||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||||
if db_field.name in ("current_websites", "alternative_domains"):
|
logger.info("timing formfield_for_manytomany")
|
||||||
kwargs["queryset"] = models.Website.objects.all().order_by("website") # Sort websites
|
with Timer() as t:
|
||||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
if db_field.name in ("current_websites", "alternative_domains"):
|
||||||
|
kwargs["queryset"] = models.Website.objects.all().order_by("website") # Sort websites
|
||||||
|
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||||
|
|
||||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
# Removes invalid investigator options from the investigator dropdown
|
logger.info("timing formfield_for_foreignkey")
|
||||||
if db_field.name == "investigator":
|
with Timer() as t:
|
||||||
kwargs["queryset"] = User.objects.filter(is_staff=True)
|
# Removes invalid investigator options from the investigator dropdown
|
||||||
return db_field.formfield(**kwargs)
|
if db_field.name == "investigator":
|
||||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
kwargs["queryset"] = User.objects.filter(is_staff=True)
|
||||||
|
return db_field.formfield(**kwargs)
|
||||||
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
if obj and obj.creator.status != models.User.RESTRICTED:
|
logger.info("timing save_model")
|
||||||
if change: # Check if the application is being edited
|
with Timer() as t:
|
||||||
# Get the original application from the database
|
if obj and obj.creator.status != models.User.RESTRICTED:
|
||||||
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
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 (
|
if (
|
||||||
obj
|
obj
|
||||||
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
||||||
and obj.status != models.DomainApplication.ApplicationStatus.APPROVED
|
and obj.status != models.DomainApplication.ApplicationStatus.APPROVED
|
||||||
and not obj.domain_is_not_active()
|
and not obj.domain_is_not_active()
|
||||||
):
|
):
|
||||||
# If an admin tried to set an approved application to
|
# If an admin tried to set an approved application to
|
||||||
# another status and the related domain is already
|
# another status and the related domain is already
|
||||||
# active, shortcut the action and throw a friendly
|
# active, shortcut the action and throw a friendly
|
||||||
# error message. This action would still not go through
|
# error message. This action would still not go through
|
||||||
# shortcut or not as the rules are duplicated on the model,
|
# shortcut or not as the rules are duplicated on the model,
|
||||||
# but the error would be an ugly Django error screen.
|
# but the error would be an ugly Django error screen.
|
||||||
|
|
||||||
# Clear the success message
|
# Clear the success message
|
||||||
messages.set_level(request, messages.ERROR)
|
messages.set_level(request, messages.ERROR)
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted. The domain is already active.",
|
"This action is not permitted. The domain is already active.",
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if obj.status != original_obj.status:
|
if obj.status != original_obj.status:
|
||||||
status_method_mapping = {
|
status_method_mapping = {
|
||||||
models.DomainApplication.ApplicationStatus.STARTED: None,
|
models.DomainApplication.ApplicationStatus.STARTED: None,
|
||||||
models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit,
|
models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit,
|
||||||
models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review,
|
models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review,
|
||||||
models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed,
|
models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed,
|
||||||
models.DomainApplication.ApplicationStatus.APPROVED: obj.approve,
|
models.DomainApplication.ApplicationStatus.APPROVED: obj.approve,
|
||||||
models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw,
|
models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw,
|
||||||
models.DomainApplication.ApplicationStatus.REJECTED: obj.reject,
|
models.DomainApplication.ApplicationStatus.REJECTED: obj.reject,
|
||||||
models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice),
|
models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice),
|
||||||
}
|
}
|
||||||
selected_method = status_method_mapping.get(obj.status)
|
selected_method = status_method_mapping.get(obj.status)
|
||||||
if selected_method is None:
|
if selected_method is None:
|
||||||
logger.warning("Unknown status selected in django admin")
|
logger.warning("Unknown status selected in django admin")
|
||||||
else:
|
else:
|
||||||
# This is an fsm in model which will throw an error if the
|
# This is an fsm in model which will throw an error if the
|
||||||
# transition condition is violated, so we roll back the
|
# transition condition is violated, so we roll back the
|
||||||
# status to what it was before the admin user changed it and
|
# status to what it was before the admin user changed it and
|
||||||
# let the fsm method set it.
|
# let the fsm method set it.
|
||||||
obj.status = original_obj.status
|
obj.status = original_obj.status
|
||||||
selected_method()
|
selected_method()
|
||||||
|
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
else:
|
else:
|
||||||
# Clear the success message
|
# Clear the success message
|
||||||
messages.set_level(request, messages.ERROR)
|
messages.set_level(request, messages.ERROR)
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted for applications with a restricted creator.",
|
"This action is not permitted for applications with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Set the read-only state on form elements.
|
"""Set the read-only state on form elements.
|
||||||
|
@ -941,36 +987,41 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
admin user permissions and the application creator's status, so
|
admin user permissions and the application creator's status, so
|
||||||
we'll use the baseline readonly_fields and extend it as needed.
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
"""
|
"""
|
||||||
|
logger.info("timing get_readonly_fields")
|
||||||
|
with Timer() as t:
|
||||||
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
|
||||||
readonly_fields = list(self.readonly_fields)
|
# Check if the creator is restricted
|
||||||
|
if obj and obj.creator.status == models.User.RESTRICTED:
|
||||||
|
# For fields like CharField, IntegerField, etc., the widget used is
|
||||||
|
# straightforward and the readonly_fields list can control their behavior
|
||||||
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
|
# Add the multi-select fields to readonly_fields:
|
||||||
|
# Complex fields like ManyToManyField require special handling
|
||||||
|
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
||||||
|
|
||||||
# Check if the creator is restricted
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
if obj and obj.creator.status == models.User.RESTRICTED:
|
return readonly_fields
|
||||||
# For fields like CharField, IntegerField, etc., the widget used is
|
# Return restrictive Read-only fields for analysts and
|
||||||
# straightforward and the readonly_fields list can control their behavior
|
# users who might not belong to groups
|
||||||
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
# Add the multi-select fields to readonly_fields:
|
|
||||||
# Complex fields like ManyToManyField require special handling
|
|
||||||
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
# Return restrictive Read-only fields for analysts and
|
|
||||||
# users who might not belong to groups
|
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
|
||||||
return readonly_fields
|
|
||||||
|
|
||||||
def display_restricted_warning(self, request, obj):
|
def display_restricted_warning(self, request, obj):
|
||||||
if obj and obj.creator.status == models.User.RESTRICTED:
|
logger.info("timing display_restricted_warning")
|
||||||
messages.warning(
|
with Timer() as t:
|
||||||
request,
|
if obj and obj.creator.status == models.User.RESTRICTED:
|
||||||
"Cannot edit an application with a restricted creator.",
|
messages.warning(
|
||||||
)
|
request,
|
||||||
|
"Cannot edit an application with a restricted creator.",
|
||||||
|
)
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
obj = self.get_object(request, object_id)
|
logger.info("timing change_view")
|
||||||
self.display_restricted_warning(request, obj)
|
with Timer() as t:
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
obj = self.get_object(request, object_id)
|
||||||
|
self.display_restricted_warning(request, obj)
|
||||||
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
|
||||||
class TransitionDomainAdmin(ListHeaderAdmin):
|
class TransitionDomainAdmin(ListHeaderAdmin):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue