Reputation: 957
I need form, where user can select tours based on selected tag. I have implemented it via view, custom html form and some AJAX stuff:
HTML form:
<form id="add-destination" method="post">
{% csrf_token %}
<select name="tag_id" id="tag_id">
<option value=''>---</option>
{% for tag in tags %}
<option value={{ tag.id }}>{{ tag.name }}</option>
{% endfor %}
</select></br>
<select multiple="multiple" name="tours" id="tours">
</select></br>
<button type="submit">Submit</button>
</form>
View to get tours by tag
def get_tours_by_tag(request):
tag_id = Tag.objects.get(id=request.GET.get('tag_id', None))
tours = Inbound.objects.filter(tags=tag_id)
return JsonResponse({"status": "ok", "tours": serializers.serialize("json", list(tours))})
JS:
$(function(){
$('#tag_id').change(function(e){
$('#tours').html('');
e.preventDefault();
$.ajax({
type: 'GET',
data: $(e.currentTarget).serialize(),
url: "/ru/tour/get_tours/",
success: function(data){
var tour_data = JSON.parse(data.tours);
if(data.status=='ok'){
$.each(tour_data, function(i, item) {
$('#tours').append('<option value='+item['pk']+'>'+item['fields']['title']+'</option>');
});
}else{
console.log("Not okay!")
}
}
});
})
});
This way works fine, but I know that it's not right way to solve such problem. And I want to do it by using power of django forms.
So I have model Tours:
class Tour(models.Model):
title = models.CharField(max_length=255)
tags = models.ManyToManyField(Tag)
And form:
class FiveDestinationForm(forms.Form):
tags = forms.ModelChoiceField(queryset=Tag.objects.all())
tours = forms.ModelMultipleChoiceField(queryset=????)
fields = ('tag', 'tours')
I found some close enough questions and tried to solve my problem by overriding __init__
method in form, as it was suggested:
class MyForm(forms.Form):
tags = forms.ModelChoiceField(queryset=Tag.objects.all())
tours = forms.ModelMultipleChoiceField(queryset=Tour.objects.none())
def __init__(self, *args, **kwargs):
qs = kwargs.pop('tours', None)
super(MyForm, self).__init__(*args, **kwargs)
self.fields['tours'].queryset = qs
fields = ('tag', 'tours')
But I get following error:
'NoneType' object has no attribute 'iterator'
So my question is how to properly set queryset for ModelMultipleChoiceField based on what user chosed in ModelChoiceField ?
Upvotes: 1
Views: 1251
Reputation: 518
The error you're seeing is most probably because the Form does not receive a 'tours' argument (it will receive a dictionary named data
instead, containing the fields' data in an unvalidated form) and thus the qs
variable will be None.
You should probably do something like this:
class MyForm(forms.Form):
tag = forms.ModelChoiceField(queryset=Tag.objects.all())
tours = forms.ModelMultipleChoiceField(queryset=Tour.objects.none())
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
if kwargs['data']:
tag_id = kwargs['data'].get('tag', None)
if tag_id:
self.fields['tours'].queryset = Tour.objects.filter(tag_id=tag_id)
Note that we bypass django's form mechanics a bit here and need to parse the tag_id ourselves. It's not a big deal, though.
From a UX vantage point, this is a rather hard problem to get right without the use of ajax. So I wouldn't completely dismiss your solution either.
Upvotes: 2