mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-24 03:30:50 +02:00
Merge branch 'main' into za/1676-require-investigator-da
This commit is contained in:
commit
07058c3abe
3 changed files with 118 additions and 9 deletions
|
@ -19,17 +19,94 @@ from registrar.utility import csv_export
|
||||||
from registrar.utility.errors import ApplicationStatusError, FSMErrorCodes
|
from registrar.utility.errors import ApplicationStatusError, FSMErrorCodes
|
||||||
from registrar.views.utility.mixins import OrderableFieldsMixin
|
from registrar.views.utility.mixins import OrderableFieldsMixin
|
||||||
from django.contrib.admin.views.main import ORDER_VAR
|
from django.contrib.admin.views.main import ORDER_VAR
|
||||||
|
from registrar.widgets import NoAutocompleteFilteredSelectMultiple
|
||||||
from . import models
|
from . import models
|
||||||
from auditlog.models import LogEntry # type: ignore
|
from auditlog.models import LogEntry # type: ignore
|
||||||
from auditlog.admin import LogEntryAdmin # type: ignore
|
from auditlog.admin import LogEntryAdmin # type: ignore
|
||||||
from django_fsm import TransitionNotAllowed # type: ignore
|
from django_fsm import TransitionNotAllowed # type: ignore
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
|
from django.contrib.auth.forms import UserChangeForm, UsernameField
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class MyUserAdminForm(UserChangeForm):
|
||||||
|
"""This form utilizes the custom widget for its class's ManyToMany UIs.
|
||||||
|
|
||||||
|
It inherits from UserChangeForm which has special handling for the password and username fields."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.User
|
||||||
|
fields = "__all__"
|
||||||
|
field_classes = {"username": UsernameField}
|
||||||
|
widgets = {
|
||||||
|
"groups": NoAutocompleteFilteredSelectMultiple("groups", False),
|
||||||
|
"user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DomainInformationAdminForm(forms.ModelForm):
|
||||||
|
"""This form utilizes the custom widget for its class's ManyToMany UIs."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.DomainInformation
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"other_contacts": NoAutocompleteFilteredSelectMultiple("other_contacts", False),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DomainInformationInlineForm(forms.ModelForm):
|
||||||
|
"""This form utilizes the custom widget for its class's ManyToMany UIs."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.DomainInformation
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"other_contacts": NoAutocompleteFilteredSelectMultiple("other_contacts", False),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
|
"""Custom form to limit transitions to available transitions.
|
||||||
|
This form utilizes the custom widget for its class's ManyToMany UIs."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.DomainApplication
|
||||||
|
fields = "__all__"
|
||||||
|
widgets = {
|
||||||
|
"current_websites": NoAutocompleteFilteredSelectMultiple("current_websites", False),
|
||||||
|
"alternative_domains": NoAutocompleteFilteredSelectMultiple("alternative_domains", False),
|
||||||
|
"other_contacts": NoAutocompleteFilteredSelectMultiple("other_contacts", False),
|
||||||
|
}
|
||||||
|
|
||||||
|
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, application.get_status_display())]
|
||||||
|
|
||||||
|
transitions = get_available_FIELD_transitions(
|
||||||
|
application, models.DomainApplication._meta.get_field("status")
|
||||||
|
)
|
||||||
|
|
||||||
|
for transition in transitions:
|
||||||
|
available_transitions.append((transition.target, transition.target.label))
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
# Based off of this excellent example: https://djangosnippets.org/snippets/10471/
|
# Based off of this excellent example: https://djangosnippets.org/snippets/10471/
|
||||||
class MultiFieldSortableChangeList(admin.views.main.ChangeList):
|
class MultiFieldSortableChangeList(admin.views.main.ChangeList):
|
||||||
"""
|
"""
|
||||||
|
@ -289,6 +366,8 @@ class UserContactInline(admin.StackedInline):
|
||||||
class MyUserAdmin(BaseUserAdmin):
|
class MyUserAdmin(BaseUserAdmin):
|
||||||
"""Custom user admin class to use our inlines."""
|
"""Custom user admin class to use our inlines."""
|
||||||
|
|
||||||
|
form = MyUserAdminForm
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
"""Contains meta information about this class"""
|
"""Contains meta information about this class"""
|
||||||
|
|
||||||
|
@ -674,6 +753,8 @@ class DomainInvitationAdmin(ListHeaderAdmin):
|
||||||
class DomainInformationAdmin(ListHeaderAdmin):
|
class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
"""Customize domain information admin class."""
|
"""Customize domain information admin class."""
|
||||||
|
|
||||||
|
form = DomainInformationAdminForm
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"domain",
|
"domain",
|
||||||
|
@ -891,6 +972,8 @@ class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain applications admin class."""
|
"""Custom domain applications admin class."""
|
||||||
|
|
||||||
|
form = DomainApplicationAdminForm
|
||||||
|
|
||||||
class InvestigatorFilter(admin.SimpleListFilter):
|
class InvestigatorFilter(admin.SimpleListFilter):
|
||||||
"""Custom investigator filter that only displays users with the manager role"""
|
"""Custom investigator filter that only displays users with the manager role"""
|
||||||
|
|
||||||
|
@ -910,13 +993,19 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Annotate the full name and return a values list that lookups can use
|
# Annotate the full name and return a values list that lookups can use
|
||||||
privileged_users_annotated = privileged_users.annotate(
|
privileged_users_annotated = (
|
||||||
full_name=Coalesce(
|
privileged_users.annotate(
|
||||||
Concat("investigator__first_name", Value(" "), "investigator__last_name", output_field=CharField()),
|
full_name=Coalesce(
|
||||||
"investigator__email",
|
Concat(
|
||||||
output_field=CharField(),
|
"investigator__first_name", Value(" "), "investigator__last_name", output_field=CharField()
|
||||||
|
),
|
||||||
|
"investigator__email",
|
||||||
|
output_field=CharField(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
).values_list("investigator__id", "full_name")
|
.values_list("investigator__id", "full_name")
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
|
||||||
return privileged_users_annotated
|
return privileged_users_annotated
|
||||||
|
|
||||||
|
@ -992,8 +1081,6 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
]
|
]
|
||||||
search_help_text = "Search by domain or submitter."
|
search_help_text = "Search by domain or submitter."
|
||||||
|
|
||||||
# Detail view
|
|
||||||
form = DomainApplicationAdminForm
|
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {"fields": ["status", "rejection_reason", "investigator", "creator", "approved_domain", "notes"]}),
|
(None, {"fields": ["status", "rejection_reason", "investigator", "creator", "approved_domain", "notes"]}),
|
||||||
(
|
(
|
||||||
|
@ -1270,6 +1357,8 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
classes conflict, so we'll just pull what we need
|
classes conflict, so we'll just pull what we need
|
||||||
from DomainInformationAdmin"""
|
from DomainInformationAdmin"""
|
||||||
|
|
||||||
|
form = DomainInformationInlineForm
|
||||||
|
|
||||||
model = models.DomainInformation
|
model = models.DomainInformation
|
||||||
|
|
||||||
fieldsets = DomainInformationAdmin.fieldsets
|
fieldsets = DomainInformationAdmin.fieldsets
|
||||||
|
|
|
@ -162,7 +162,11 @@ function initializeWidgetOnToList(toList, toListId) {
|
||||||
'websites': '/admin/registrar/website/__fk__/change/?_to_field=id',
|
'websites': '/admin/registrar/website/__fk__/change/?_to_field=id',
|
||||||
'alternative_domains': '/admin/registrar/website/__fk__/change/?_to_field=id',
|
'alternative_domains': '/admin/registrar/website/__fk__/change/?_to_field=id',
|
||||||
},
|
},
|
||||||
false,
|
// NOTE: If we open view in the same window then use the back button
|
||||||
|
// to go back, the 'chosen' list will fail to initialize correctly in
|
||||||
|
// sandbozes (but will work fine on local). This is related to how the
|
||||||
|
// Django JS runs (SelectBox.js) and is probably due to a race condition.
|
||||||
|
true,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
16
src/registrar/widgets.py
Normal file
16
src/registrar/widgets.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# widgets.py
|
||||||
|
|
||||||
|
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
|
|
||||||
|
class NoAutocompleteFilteredSelectMultiple(FilteredSelectMultiple):
|
||||||
|
"""Firefox and Edge are unable to correctly initialize the source select in filter_horizontal
|
||||||
|
widgets. We add the attribute autocomplete=off to fix that."""
|
||||||
|
|
||||||
|
def render(self, name, value, attrs=None, renderer=None):
|
||||||
|
if attrs is None:
|
||||||
|
attrs = {}
|
||||||
|
attrs["autocomplete"] = "off"
|
||||||
|
output = super().render(name, value, attrs=attrs, renderer=renderer)
|
||||||
|
return mark_safe(output) # nosec
|
Loading…
Add table
Add a link
Reference in a new issue