In questo caso basta un semplice esempio per capirne l’utilizzo.
from autocomplete import autocomplete
from autocomplete import AutocompleteChoiceField
from modello import Prodotto
# registrazione normale. La query viene fatta usando i campi scelti in search_fields
#
autocomplete.register('prodotto', queryset = Prodotto.objects.filter(status=1), search_fields=('name',), limit=10)
# registrazione con vista personalizzata. La gestione della ricerca viene fatta nella vista
# scelta nel parametro viws. Alla vista verranno passati in automatico due parametri: request e query.
# La vista dovra' tornare obbligatoriamente un queryset. (Un esempio di vista e' in basso)
#
def label_custom_product(obj):
return "%s - %s" % (obj.codice, obj.descrizione)
autocomplete.register('prodotto', queryset = Prodotto.objects.filter(status=1),
views='modulo.vista.funzione_personalizzata',
limit=10, label=label_custom_product)
class ProdottoAdminForm(forms.ModelForm):
class Meta:
model = Prodotto
def __init__(self, *args, **kws):
super(ProdottoAdminForm, self).__init__(*args, **kws)
# se si vuole avere una select con singola selezione
self.fields['prodotto'] = AutocompleteChoiceField('prodotto', label=_("Prodotto"))
# se si vuole avere una select con selezione multipla
#self.fields['prodotto'] = AutocompleteMultiChoiceField('prodotto', label=_("Prodotto"))
class ProdottoAdmin(admin.ModelAdmin):
form = ProdottoAdminForm
from modello import Prodotto
def funzione_personalizzata(request, query):
return Prodotto.objects.filter(nome__icontains=query)
Gli inline di Django funzionano in questa maniera:
- viene renderizzata una riga MASTER nascosta
- ad ogni click di “aggiungi un altro” viene clonata la riga MASTER nascosta cambiandogli di volta in volta un numero
Questo comportamento crea problemi quando ad un widget della riga nascosta viene associato un javascript. Quest’ultimo, infatti, non viene replicato nel clone di jquery, causando il non funzionamento dell’autocomplete.
Per ovviare a questo problema si e’ deciso di utilizzare un CharField che nel render evita di attaccargli il javascript dell’autocomplete. Questo pero’ comporta una gestione manuale della chiamata javascript che collega l’autocomplete all’ input text. Si e’ scelto un CharField invece di una select per una maggiore semplicita’ nel popolamento iniziale dell’autocomplete. Negli inline, a differenza dei form normali, la popolazione iniziale dell’autocomplete deve essere fatta in javascript (nei form normali invece viene fatta in automatico direttamente nella renderizzazione del widget). Quindi il metodo piu’ semplice era leggere il conenuto del text box e passarlo come valore iniziale dell’autocomplete dopo averlo “evaluato”. Se si fosse usata una select il passaggio dei parametri sarebbe diventato piu’ complicato perche’ bisognava costruirsi un array di dizionari a partire dalle option della select (cosa non che non si possa fare, ma ho scelto la strada piu’ breve).
Come sopra (cambia solo la scelta del field) Indicare il template da utilizzare per l’inline
admin/ordine/ordine/tabular_riga_ordine.html
Cosi’ facendo ho la possibilita’ di personalizzare il javascript del mio inline permettendomi di sovrascrivere/modificare la parte che crea gli inline “live”.
Note
Per gli inline BISOGNA PER FORZA utilizzare il field AutocompleteInlineCharField.
Creare il template tabular_riga_rdine.html copiando o il template django/contrib/admin/templates/admin/edit_inline/tabular.html o django/contrib/admin/templates/admin/edit_inline/stacked.html in base al tipo di inline che si vule renderizzare. La parte da modificare sara’ quella relativa al blocco del javascript
<script type="text/javascript">
(function($) {
$(document).ready(function($) {
var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
var ac_name = "prodotto"; // nome dato in admin.py all'atto della registrazione dell'autocomplete
alternatingRows = function(row) {
$(rows).not(".add-row").removeClass("row1 row2")
.filter(":even").addClass("row1").end()
.filter(rows + ":odd").addClass("row2");
// sulla riga appena creata cerco l'input type text del prodotto
// e gli collego l'autocomplete
ii = row.find(".product input");
$(ii).tokenInput("/autocomplete/" + ac_name + "/", {"tokenLimit":1, "useTextInput":true});
}
$(rows).formset({
prefix: "{{ inline_admin_formset.formset.prefix }}",
addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
deleteCssClass: "inline-deletelink",
deleteText: "{% trans "Remove" %}",
emptyCssClass: "empty-form",
removed: alternatingRows,
added: alternatingRows
});
// Collego l'autocomplete agli input type text del prodotto di tutte le righe gia esistenti
$("tr.has_original td.prodotto input").each(function(i) {
$(this).tokenInput("/autocomplete/" + ac_name + "/", {"tokenLimit":1, "useTextInput":true, "prePopulate": eval($(this).val())});
});
});
})(jQuery.noConflict());
</script>
Le righe aggiunte rispetto all’originale sono
var ac_name = "prodotto"; // nome dato in admin.py all'atto della registrazione dell'autocomplete
// sulla riga appena creata cerco l'input type text del prodotto
// e gli collego l'autocomplete
ii = row.find(".prodotto input");
$(ii).tokenInput("/autocomplete/" + ac_name + "/", {"tokenLimit":1, "useTextInput":true});
// Collego l'autocomplete agli input type text del prodotto di tutte le righe gia esistenti
$("tr.has_original td.prodotto input").each(function(i) {
$(this).tokenInput("/autocomplete/" + ac_name + "/", {"tokenLimit":1, "useTextInput":true, "prePopulate": eval($(this).val())});
});
Nelle righe sopra gli unici due parametri che bisognera’ modificare di volta in volta sono l’ac_name che dovra’ essere settato con il nome scelto nell’admin.py quando si e’ registrata l’autocomplete e il nome della classe che viene cercata tramite jquery per collegare l’autocomplete al widget giusto (nel caso sopra e’ prodotto)