Django/AJAX - 链式模型选择 - 修复 'Select a Valid Choice' 错误而不预加载所有可能的选择
Django/AJAX - Chained modelform selects - Fixing 'Select a Valid Choice' error without preloading of all possible choices
我有这些型号:
class Subject(models.Model):
title = models.CharField(max_length=255)
class Teacher(models.Model):
name = models.CharField(max_length=255)
subjects = models.ManyToManyField(Subject)
class Exam(models.Model):
classroom = models.CharField(max_length=20)
teacher = models.ForeignKey(Teacher)
subjects = models.ManyToManyField(Subject)
因此,由于有几千个科目,但只有少数科目对特定教师有效,我禁用了模型中的科目字段,一旦选择了教师,重新启用它并加载可能的通过 AJAX 的选项。
编辑现有考试时,一切正常,因为我可以这样做:
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subject_set.all()
但是,当我想添加新考试时,加载变得非常缓慢,因为所有可能的选项都已加载,只有这样我的 javascript 才能用更窄的查询集替换它们。因此,我尝试像这样删除冗余加载:
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subject_set.all()
else:
self.fields['subjects'].queryset = Subject.objects.none()
然后我得到一个 "Select a Valid Choice. XXX is not one of the available choices" 错误。
那么,有没有办法动态更改特定字段的查询集参数(或其他一些解决方法)并避免出现指定的错误?
编辑:
好吧,看来我的问题解释不通,没人回答,所以这里是一个简化的应用程序的完整代码,你可以自己尝试一下,看看问题是什么。
models.py
class Subject(models.Model):
title = models.CharField(max_length=255)
def __unicode__(self):
return self.title
class Teacher(models.Model):
name = models.CharField(max_length=255)
subjects = models.ManyToManyField(Subject)
def __unicode__(self):
return self.name
class Exam(models.Model):
classroom = models.CharField(max_length=20)
teacher = models.ForeignKey(Teacher)
subjects = models.ManyToManyField(Subject)
def __unicode__(self):
return u"{0} - {1}".format(self.classroom, self.teacher)
forms.py/admin.py
from .models import Subject, Teacher, Exam
class ExamForm(forms.ModelForm):
class Meta:
model = Exam
fields = ['classroom','teacher','subjects']
class Media:
js = (
'https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js',
'js/exam_form.js',
)
def __init__(self, *args, **kwargs):
super(ExamForm, self).__init__(*args, **kwargs)
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subjects.all()
else:
self.fields['subjects'].queryset = Subject.objects.none()
@admin.register(Exam)
class ExamAdmin(admin.ModelAdmin):
list_display = ['classroom','teacher']
form = ExamForm
admin.site.register(Subject)
admin.site.register(Teacher)
views.py
from .models import Teacher
def query_teacher_subject(request):
id = request.GET.get('id')
if request.is_ajax():
try:
teacher = Teacher.objects.get(id=id)
except Teacher.DoesNotExist:
return HttpResponse(serializers.serialize('json', 'Teacher does not exist.'))
else:
return HttpResponse(serializers.serialize('json',teacher.subjects.all(), fields=('id','title',)))
else:
raise Http404
exam_form.js
$(function() {
var t = $("#id_teacher");
t.change(function(){
if (t.val() != '') {
$("#id_subjects").prop('disabled',false);
var selected = [];
$("#id_subjects :selected").each(function(i, sel){
selected.push($(sel).val());
});
$.get('/ajax-call/', {id:t.val()}, function(data) {
data = $.parseJSON(data);
var options;
$.each(data, function(index,item) {
options += '<option value="' + item.pk + '">' + item.fields.title + '</option>';
});
$("#id_subjects").html(options);
$("#id_subjects option").each(function(i, sel) {
if ($.inArray(sel.value, selected) !== -1) {
$('#id_subjects option[value=' + sel.value + ']').attr('selected', true);
}
});
});
} else {
$("#id_subjects").prop('disabled',true);
}
});
$("#id_teacher").trigger('change');
});
因此,如果您在 ExamForm 的 init 方法中省略 else 分支,则一切正常,除了所有主题最初都已加载这一事实。但是,如果将其留在那里,则会出现以下错误:"Select a valid choice. 1 is not one of the available choices."
那么,如何将最初为空的查询集放入稍后通过 AJAX 填充并仍然成功验证的模型中?
好吧,我终于解决了问题,但我仍然不确定为什么原来的想法不起作用。
所以,我单独留下了表单的 __init__ 方法,并将调整移动到管理员的 get_form 方法,如下所示:
def get_form(self, request, obj=None, **kwargs):
form = super(ExamAdmin, self).get_form(request, obj, **kwargs)
if request.method == 'GET':
if obj:
form.base_fields['subjects'].queryset = Subject.objects.filter(pk__in=obj.subjects.all())
else:
form.base_fields['subjects'].queryset = Subject.objects.none()
return form
我有这些型号:
class Subject(models.Model):
title = models.CharField(max_length=255)
class Teacher(models.Model):
name = models.CharField(max_length=255)
subjects = models.ManyToManyField(Subject)
class Exam(models.Model):
classroom = models.CharField(max_length=20)
teacher = models.ForeignKey(Teacher)
subjects = models.ManyToManyField(Subject)
因此,由于有几千个科目,但只有少数科目对特定教师有效,我禁用了模型中的科目字段,一旦选择了教师,重新启用它并加载可能的通过 AJAX 的选项。
编辑现有考试时,一切正常,因为我可以这样做:
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subject_set.all()
但是,当我想添加新考试时,加载变得非常缓慢,因为所有可能的选项都已加载,只有这样我的 javascript 才能用更窄的查询集替换它们。因此,我尝试像这样删除冗余加载:
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subject_set.all()
else:
self.fields['subjects'].queryset = Subject.objects.none()
然后我得到一个 "Select a Valid Choice. XXX is not one of the available choices" 错误。
那么,有没有办法动态更改特定字段的查询集参数(或其他一些解决方法)并避免出现指定的错误?
编辑:
好吧,看来我的问题解释不通,没人回答,所以这里是一个简化的应用程序的完整代码,你可以自己尝试一下,看看问题是什么。
models.py
class Subject(models.Model):
title = models.CharField(max_length=255)
def __unicode__(self):
return self.title
class Teacher(models.Model):
name = models.CharField(max_length=255)
subjects = models.ManyToManyField(Subject)
def __unicode__(self):
return self.name
class Exam(models.Model):
classroom = models.CharField(max_length=20)
teacher = models.ForeignKey(Teacher)
subjects = models.ManyToManyField(Subject)
def __unicode__(self):
return u"{0} - {1}".format(self.classroom, self.teacher)
forms.py/admin.py
from .models import Subject, Teacher, Exam
class ExamForm(forms.ModelForm):
class Meta:
model = Exam
fields = ['classroom','teacher','subjects']
class Media:
js = (
'https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js',
'js/exam_form.js',
)
def __init__(self, *args, **kwargs):
super(ExamForm, self).__init__(*args, **kwargs)
if not self.instance._state.adding:
self.fields['subjects'].queryset = self.instance.teacher.subjects.all()
else:
self.fields['subjects'].queryset = Subject.objects.none()
@admin.register(Exam)
class ExamAdmin(admin.ModelAdmin):
list_display = ['classroom','teacher']
form = ExamForm
admin.site.register(Subject)
admin.site.register(Teacher)
views.py
from .models import Teacher
def query_teacher_subject(request):
id = request.GET.get('id')
if request.is_ajax():
try:
teacher = Teacher.objects.get(id=id)
except Teacher.DoesNotExist:
return HttpResponse(serializers.serialize('json', 'Teacher does not exist.'))
else:
return HttpResponse(serializers.serialize('json',teacher.subjects.all(), fields=('id','title',)))
else:
raise Http404
exam_form.js
$(function() {
var t = $("#id_teacher");
t.change(function(){
if (t.val() != '') {
$("#id_subjects").prop('disabled',false);
var selected = [];
$("#id_subjects :selected").each(function(i, sel){
selected.push($(sel).val());
});
$.get('/ajax-call/', {id:t.val()}, function(data) {
data = $.parseJSON(data);
var options;
$.each(data, function(index,item) {
options += '<option value="' + item.pk + '">' + item.fields.title + '</option>';
});
$("#id_subjects").html(options);
$("#id_subjects option").each(function(i, sel) {
if ($.inArray(sel.value, selected) !== -1) {
$('#id_subjects option[value=' + sel.value + ']').attr('selected', true);
}
});
});
} else {
$("#id_subjects").prop('disabled',true);
}
});
$("#id_teacher").trigger('change');
});
因此,如果您在 ExamForm 的 init 方法中省略 else 分支,则一切正常,除了所有主题最初都已加载这一事实。但是,如果将其留在那里,则会出现以下错误:"Select a valid choice. 1 is not one of the available choices."
那么,如何将最初为空的查询集放入稍后通过 AJAX 填充并仍然成功验证的模型中?
好吧,我终于解决了问题,但我仍然不确定为什么原来的想法不起作用。
所以,我单独留下了表单的 __init__ 方法,并将调整移动到管理员的 get_form 方法,如下所示:
def get_form(self, request, obj=None, **kwargs):
form = super(ExamAdmin, self).get_form(request, obj, **kwargs)
if request.method == 'GET':
if obj:
form.base_fields['subjects'].queryset = Subject.objects.filter(pk__in=obj.subjects.all())
else:
form.base_fields['subjects'].queryset = Subject.objects.none()
return form