Reputation: 11
I don't know if this is even possible, any way, I currently have something as the following:
class Incidence(models.Model):
...
instalation = models.ForeignKey('Instalation')
machine = models.ManyToManyField('Machine')
...
class Machine(models.Model):
...
instalation = models.ForeignKey('Instalation')
...
So Machines
belongs to instalations
and incidences
are related to machines
and incidences
, the idea is to put a dynamic FilteredSelectMultiple
widget to select the machines
related with the incidence
in the admin page. The admin currently is something as:
class IncidenceMachineForm(forms.ModelForm):
filtered_machine = ModelMultipleChoiceField(
queryset=Machine.objects.order_by('hostname'),
required=False, widget=FilteredSelectMultiple("filtered machine name", is_stacked=False)
)
class Meta:
model = Incidence
And then, the modelAdmin
uses the form IncidenceMachineForm
. The idea is that when you select the instalation
of the incidence
, only the machines
related to that instalation
are available for selection. I guess something as this is not possible:
queryset=Machine.objects.filter(instalation=self.instalation).order_by('hostname'),
Any ideas will be highly appreciated. Thanks!
Upvotes: 1
Views: 3165
Reputation: 703
from django.contrib.admin.widgets import FilteredSelectMultiple
@admin.register(YourModel)
class YourModelAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs['widget'] = FilteredSelectMultiple(
db_field.verbose_name,
False,
)
return super().formfield_for_manytomany(db_field, request, **kwargs)
fast and don't need to override ModelForm or etc. effect all m2m fields.
Upvotes: 0
Reputation: 798
I notice that FilteredSelectMultiple widget has already cached, converted and changed the name of original widget after the page is loaded, so changing the "option" list of "select" tag is not enough.
I came up with this solution:
Here is the code I have tested:
$('#id_instalation').change(function() {
var selected = $('#id_instalation').val();
if(selected) {
$.ajax({
url: '/url/to/get/machines/' + selected,
success: function(list) {
var options = [];
options.push('<select multiple="multiple" class="selectfilter" name="machine" id="id_machine">');
for(i in list){
options.push('<option value="' + list[i][0] + '">' +
list[i][1] + '</option>');
}
options.push('</select>');
$('#machine_wrapper').html(options.join(''));
// Change title of widget
var title = $('#id_instalation option:selected"').text().toLowerCase();
SelectFilter.init("id_machine", title, 0, "/path/to/django/media/");
},
error: function() {
alert('Server error');
},
});
}
}
This is the sample of data returned from ajax call:
[[1, "Machine 1"], [2, "Machine 2"], [3, "Machine 3"]]
For server side implementation, please see Chris Pratt's answer
Note: tested with:
Upvotes: 2
Reputation: 239300
You can do that after the model has been saved, and there's an instalation
associated with it to use (though the lookup would be instalation=self.instance.instalation
).
However, that doesn't do you much good, because if a different instalation
is selected the list would still be the one for the old selection, and obviously you get no help when first creating the object.
As a result, the only way to accomplish this is with AJAX. You create a view to receive the selected instalation
id, and return a JSON response consisting of machines
associated with it. Tie the view into your urlconf, and then hit it with AJAX and update the select box based on the results.
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import simplejson
def ajax_admin_get_machines_for_instalation(request):
instalation_id = request.GET.get('instalation_id')
if instalation_id is None:
# instalation_id wasn't provided so return all machines
machines_qs = Machine.objects.all()
else:
instalation = get_object_or_404(Instalation, pk=instalation_id)
machines_qs = Machine.objects.filter(instalation=instalation)
# 'name' is the field you want to use for the display value
machines = machines_qs.values('pk', 'name')
return HttpResponse(simplejson.dumps(machines), mimetype='application/json')
Then the JS:
(function($){
$(document).ready(function(){
function update_machine_options(){
var selected = $('#id_instalation').val();
if (selected) {
$.getJSON('/url/for/ajax/view/', {
instalation_id: selected
}, function(data, jqXHR){
var options = [];
for (k in data) {
options.append('<option value="'+data[k].pk+'">'+data[k].name+'</option>');
}
$('#id_machine').html(options.join(''));
});
}
}
update_machine_options();
$('#id_instalation').change(function(){
update_machine_options();
});
});
})(django.jQuery);
Upvotes: 1