Hey I recently created a topic on django issue tracker and I was advised to first open a discussion here. Link to the topic:
I feel like this class could be more modular, for example here is my simple idea that would allow writing an autocomplete method for filter on the ModelAdmin class:
from django.apps import apps
from django.contrib.admin.exceptions import NotRegistered
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
from django.core.exceptions import PermissionDenied
class AutoCompleteXJsonView(AutocompleteJsonView):
def is_autocompletex_request(self, request):
return any(field.startswith('autocompletex_') for field in request.GET.keys())
def get_autocompletex_search_results(self, request, queryset, term):
source_model_admin = self.get_source_model_admin(request)
field_name = request.GET["field_name"]
if autocompletex_method := getattr(source_model_admin, f'autocompletex_{field_name}', None):
return autocompletex_method(request, queryset, term, source_model_admin)
return self.model_admin.get_search_results(request, queryset, term)
def get_source_model_admin(self, request):
app_label = request.GET["app_label"]
model_name = request.GET["model_name"]
try:
source_model = apps.get_model(app_label, model_name)
except LookupError as e:
raise PermissionDenied from e
try:
source_model_admin = self.admin_site.get_model_admin(source_model)
except NotRegistered as e:
raise PermissionDenied from e
return source_model_admin
def get_queryset(self):
qs = self.model_admin.get_queryset(self.request)
qs = qs.complex_filter(self.source_field.get_limit_choices_to())
if self.is_autocompletex_request(self.request):
qs, search_use_distinct = self.get_autocompletex_search_results(
self.request, qs, self.term,
)
else:
qs, search_use_distinct = self.model_admin.get_search_results(
self.request, qs, self.term
)
if search_use_distinct:
qs = qs.distinct()
return qs
With addition of some fields:
from django import forms
class AutocompleteXWidget(forms.HiddenInput):
class Media:
js = (
'admin/js/vendor/jquery/jquery.js',
'admin/js/jquery.init.js',
'admin/js/autocompletex.js',
)
class AutocompleteXField(forms.JSONField):
def __init__(self, *args, **kwargs):
defaults = {
'widget': AutocompleteXWidget(),
'required': False,
}
defaults.update(kwargs)
super().__init__(*args, **defaults)
class AutoCompleteXForm(forms.ModelForm):
autocompletex = AutocompleteXField()
class AutoCompleteXMixin:
autocompletex_initial = {'field_values': []}
def get_autocompletex_initial(self):
return getattr(self, 'autocompletex_initial')
def get_changeform_initial_data(self, request):
initial = super().get_changeform_initial_data(request)
initial['autocompletex'] = self.get_autocompletex_initial()
return initial
And some javascript:
'use strict';
(function ($) {
let modifyAutocompleteRequest = function (request, $element) {
const config = JSON.parse($('#id_autocompletex').val() || '{}');
// noinspection JSUnresolvedReference
const dependencies = config.field_values || {};
dependencies.forEach(dep => {
request['autocompletex_' + dep] = $element.closest('form').find(`[name$="${dep}"]`).val() || '';
})
return request;
}
const originalDjangoAdminSelect2 = $.fn.djangoAdminSelect2;
if (typeof originalDjangoAdminSelect2 === 'function') {
$.fn.djangoAdminSelect2 = function () {
const originalSelect2 = $.fn.select2;
$.fn.select2 = function (options) {
if (options && options.ajax && typeof options.ajax.data === 'function') {
const originalData = options.ajax.data;
const $select = this;
options.ajax.data = (params) => {
let req = originalData.call(this, params);
return modifyAutocompleteRequest(req, $select);
};
}
return originalSelect2.apply(this, arguments);
};
const result = originalDjangoAdminSelect2.apply(this, arguments);
$.fn.select2 = originalSelect2;
return result;
};
}
})(django.jQuery);
You could write method as such, on the source model class instead of the model class:
@admin.register(SpeakerIdentificationTask)
class SpeakerIdentificationTaskAdmin(BaseTaskAdmin):
autocompletex_initial = {'field_values': ['input_meeting']}
def autocompletex_context_documents(self, request, queryset, search_term, model_admin, **kwargs):
queryset, use_distinct = model_admin.get_search_results(request, queryset, search_term)
meeting_id = request.GET.get('input_meeting')
if meeting_id:
queryset = queryset.filter(meetings__id=meeting_id).distinct()
return queryset, use_distinct
It’s just example of how this could work +/-.
Maybe people have better ideas.
I want to start discussion on this topic ^^
Regards
Mateusz