Last active
July 26, 2022 04:17
-
-
Save ninapavlich/9c056532624094f24bb53752c3ad294f to your computer and use it in GitHub Desktop.
Custom configuration for django-autocomplete-light plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django import forms | |
from django.contrib import admin | |
from dal import autocomplete, forward | |
class AdminAutocompleteFormMixin(forms.ModelForm): | |
class Media: | |
js = ('admin/autocomplete/forward.js', | |
'admin/autocomplete/select_admin_autocomplete.js') | |
class AdminModelSelect2(autocomplete.ModelSelect2): | |
autocomplete_function = 'admin-autocomplete' | |
# THIS IS WHERE YOU CUSTOMIZE: | |
class CustomModelAdminForm(AdminAutocompleteFormMixin): | |
class Meta: | |
model = CustomModel | |
fields = ('__all__') | |
widgets = { | |
'myotherfield': AdminModelSelect2( | |
url='admin-autocomplete', | |
attrs={'data-html': True}, | |
forward=(forward.Const(val="appname.MyOtherModel", dst="model"),) | |
) | |
} | |
@admin.register(CustomModel) | |
class CustomModelAdmin(admin.ModelAdmin): | |
form = CustomModelAdminForm | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;(function($, yl) { | |
yl.forwardHandlerRegistry = yl.forwardHandlerRegistry || {}; | |
yl.registerForwardHandler = function(name, handler) { | |
yl.forwardHandlerRegistry[name] = handler; | |
}; | |
yl.getForwardHandler = function(name) { | |
return yl.forwardHandlerRegistry[name]; | |
}; | |
function getForwardStrategy(element) { | |
var checkForCheckboxes = function() { | |
var all = true; | |
$.each(element, function(ix, e) { | |
if ($(e).attr("type") !== "checkbox") { | |
all = false; | |
} | |
}); | |
return all; | |
}; | |
if (element.length === 1 && | |
element.attr("type") === "checkbox" && | |
element.attr("value") === undefined) { | |
// Single checkbox without 'value' attribute | |
// Boolean field | |
return "exists"; | |
} else if (element.length === 1 && | |
element.attr("multiple") !== undefined) { | |
// Multiple by HTML semantics. E. g. multiple select | |
// Multiple choice field | |
return "multiple"; | |
} else if (checkForCheckboxes()) { | |
// Multiple checkboxes or one checkbox with 'value' attribute. | |
// Multiple choice field represented by checkboxes | |
return "multiple"; | |
} else { | |
// Other cases | |
return "single"; | |
} | |
} | |
/** | |
* Get fields with name `name` relative to `element` with considering form | |
* prefixes. | |
* @param element the element | |
* @param name name of the field | |
* @returns jQuery object with found fields or empty jQuery object if no | |
* field was found | |
*/ | |
yl.getFieldRelativeTo = function(element, name) { | |
var prefixes = $(element).getFormPrefixes(); | |
for (var i = 0; i < prefixes.length; i++) { | |
var fieldSelector = "[name=" + prefixes[i] + name + "]"; | |
var field = $(fieldSelector); | |
if (field.length) { | |
return field; | |
} | |
} | |
return $(); | |
}; | |
/** | |
* Get field value which is put to forwarded dictionary | |
* @param field the field | |
* @returns forwarded value | |
*/ | |
yl.getValueFromField = function(field) { | |
var strategy = getForwardStrategy(field); | |
var serializedField = $(field).serializeArray(); | |
var getSerializedFieldElementAt = function (index) { | |
// Return serializedField[index] | |
// or null if something went wrong | |
if (serializedField.length > index) { | |
return serializedField[index]; | |
} else { | |
return null; | |
} | |
}; | |
var getValueOf = function (elem) { | |
// Return elem.value | |
// or null if something went wrong | |
if (elem.hasOwnProperty("value") && | |
elem.value !== undefined | |
) { | |
return elem.value; | |
} else { | |
return null; | |
} | |
}; | |
var getSerializedFieldValueAt = function (index) { | |
// Return serializedField[index].value | |
// or null if something went wrong | |
var elem = getSerializedFieldElementAt(index); | |
if (elem !== null) { | |
return getValueOf(elem); | |
} else { | |
return null; | |
} | |
}; | |
if (strategy === "multiple") { | |
return serializedField.map( | |
function (item) { | |
return getValueOf(item); | |
} | |
); | |
} else if (strategy === "exists") { | |
return serializedField.length > 0; | |
} else { | |
return getSerializedFieldValueAt(0); | |
} | |
}; | |
yl.getForwards = function(element) { | |
var forwardElem, | |
forwardList, | |
forwardedData, | |
divSelector, | |
form; | |
divSelector = "div.dal-forward-conf#dal-forward-conf-for-" + | |
element.attr("id"); | |
form = element.length > 0 ? $(element[0].form) : $(); | |
forwardElem = | |
form.find(divSelector).find('script'); | |
if (forwardElem.length === 0) { | |
return; | |
} | |
try { | |
forwardList = JSON.parse(forwardElem.text()); | |
} catch (e) { | |
return; | |
} | |
if (!Array.isArray(forwardList)) { | |
return; | |
} | |
forwardedData = {}; | |
$.each(forwardList, function(ix, field) { | |
var srcName, dstName; | |
if (field.type === "const") { | |
forwardedData[field.dst] = field.val; | |
} else if (field.type === "self") { | |
if (field.hasOwnProperty("dst")) { | |
dstName = field.dst; | |
} else { | |
dstName = "self"; | |
} | |
forwardedData[dstName] = yl.getValueFromField(element); | |
} else if (field.type === "field") { | |
srcName = field.src; | |
if (field.hasOwnProperty("dst")) { | |
dstName = field.dst; | |
} else { | |
dstName = srcName; | |
} | |
var forwardedField = yl.getFieldRelativeTo(element, srcName); | |
if (!forwardedField.length) { | |
return; | |
} | |
forwardedData[dstName] = yl.getValueFromField(forwardedField); | |
} else if (field.type === "javascript") { | |
var handler = yl.getForwardHandler(field.handler); | |
forwardedData[field.dst || field.handler] = handler(element); | |
} | |
}); | |
return JSON.stringify(forwardedData); | |
}; | |
})(yl.jQuery, yl); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$(document).on('autocompleteLightInitialize', '[data-autocomplete-light-function=admin-autocomplete]', function() { | |
var element = $(this); | |
// Templating helper | |
function template(text, is_html) { | |
if (is_html) { | |
var $result = $('<span>'); | |
$result.html(text); | |
return $result; | |
} else { | |
return text; | |
} | |
} | |
function result_template(item) { | |
return template(item.text, | |
element.attr('data-html') !== undefined || element.attr('data-result-html') !== undefined | |
); | |
} | |
function selected_template(item) { | |
console.log("SELECTED TEMPLATE?") | |
if (item.selected_text !== undefined) { | |
return template(item.selected_text, | |
element.attr('data-html') !== undefined || element.attr('data-selected-html') !== undefined | |
); | |
} else { | |
return result_template(item); | |
} | |
return | |
} | |
var ajax = null; | |
if ($(this).attr('data-autocomplete-light-url')) { | |
ajax = { | |
url: $(this).attr('data-autocomplete-light-url'), | |
dataType: 'json', | |
delay: 250, | |
data: function (params) { | |
var data = { | |
q: params.term, // search term | |
page: params.page, | |
create: element.attr('data-autocomplete-light-create') && !element.attr('data-tags'), | |
forward: yl.getForwards(element) | |
}; | |
return data; | |
}, | |
processResults: function (data, page) { | |
if (element.attr('data-tags')) { | |
$.each(data.results, function(index, value) { | |
value.id = value.text; | |
}); | |
} | |
return data; | |
}, | |
cache: true | |
}; | |
} | |
$(this).select2({ | |
tokenSeparators: element.attr('data-tags') ? [','] : null, | |
debug: true, | |
placeholder: element.attr('data-placeholder') || '', | |
language: element.attr('data-autocomplete-light-language'), | |
minimumInputLength: element.attr('data-minimum-input-length') || 0, | |
allowClear: ! $(this).is('[required]'), | |
templateResult: result_template, | |
templateSelection: selected_template, | |
ajax: ajax, | |
tags: Boolean(element.attr('data-tags')), | |
}); | |
$(this).on('select2:selecting', function (e) { | |
var data = e.params.args.data; | |
if (data.create_id !== true) | |
return; | |
e.preventDefault(); | |
var select = $(this); | |
$.ajax({ | |
url: $(this).attr('data-autocomplete-light-url'), | |
type: 'POST', | |
dataType: 'json', | |
data: { | |
text: data.id, | |
forward: yl.getForwards($(this)) | |
}, | |
beforeSend: function(xhr, settings) { | |
xhr.setRequestHeader("X-CSRFToken", document.csrftoken); | |
}, | |
success: function(data, textStatus, jqXHR ) { | |
select.append( | |
$('<option>', {value: data.id, text: data.text, selected: true}) | |
); | |
select.trigger('change'); | |
select.select2('close'); | |
} | |
}); | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
INSTALLED_APPS = [ | |
'dal', | |
'dal_select2', | |
... | |
'django.contrib.admin', | |
'django.contrib.auth', | |
'django.contrib.contenttypes', | |
'django.contrib.sessions', | |
'django.contrib.messages', | |
'django.contrib.staticfiles', | |
'django_extensions', | |
... | |
] | |
# Also make sure static JS files are available: | |
STATICFILES_DIRS = ( | |
os.path.join(BASE_DIR, 'path', 'to', 'custom', 'static'), | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.urls import path, re_path, include | |
from .admin import AdminAutocomplete | |
urlpatterns = [ | |
re_path( | |
r'^admin/admin-autocomplete/$', | |
AdminAutocomplete.as_view(), | |
name='admin-autocomplete', | |
) | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.apps import apps | |
from django.contrib.admin.views.decorators import staff_member_required | |
from django.utils.decorators import method_decorator | |
from django.db.models import Q | |
from dal import autocomplete | |
class AdminAutocomplete(autocomplete.Select2QuerySetView): | |
autocomplete_class = None | |
@method_decorator(staff_member_required) | |
def dispatch(self, *args, **kwargs): | |
return super(AdminAutocomplete, self).dispatch(*args, **kwargs) | |
def get_queryset(self): | |
model = self.forwarded.get('model', None) | |
if not model: | |
return [] | |
app_label = model.split('.')[0] | |
model_name = model.split('.')[1] | |
if not app_label or not model_name: | |
return [] | |
# Determine which class we're working with | |
self.autocomplete_class = apps.get_model(app_label, model_name) | |
# Dynamically create query based on class's autocomplete_lookup_fields: | |
qs = self.autocomplete_class.objects.all() | |
if self.q: | |
q = Q() | |
if hasattr(self.autocomplete_class, 'get_autocomplete_lookup_fields'): | |
autocomplete_fields = self.autocomplete_class.get_autocomplete_lookup_fields() | |
else: | |
autocomplete_fields = ['pk'] | |
for autocomplete_field in autocomplete_fields: | |
kwargs = {'%s__icontains' % (autocomplete_field): self.q} | |
print(kwargs) | |
q = q | Q(**kwargs) | |
qs = qs.filter(q) | |
return qs | |
def get_result_html(self, item): | |
if hasattr(self.autocomplete_class, 'get_autocomplete_result_html'): | |
return self.autocomplete_class.get_autocomplete_result_html(item) | |
return super(AdminAutocomplete, self).get_result_label(item) | |
def get_result_label(self, item): | |
if hasattr(self.autocomplete_class, 'get_autocomplete_result_plaintext'): | |
return self.autocomplete_class.get_autocomplete_result_plaintext(item) | |
return super(AdminAutocomplete, self).get_result_label(item) | |
def get_results(self, context): | |
"""Return data for the 'results' key of the response.""" | |
return [ | |
{ | |
'id': self.get_result_value(result), | |
'selected_text': self.get_result_label(result), | |
'text': self.get_result_html(result), | |
} for result in context['object_list'] | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment