I want to build a Country/State selector. First you choose a country, and the States for that country are displayed in the 2nd select box. Doing that in PHP and jQuery is fairly easy, but I find Django forms to be a bit restrictive in that sense.
I could set the State field to be empty on page load, and then populate it with some jQuery, but then if there are form errors it won't be able to "remember" what State you had selected. I'm also pretty sure that it will throw a validation error because your choice wasn't one of the ones listed in the form on the Python side of things.
So how do I get around these problems?
Here is my solution. It uses the undocumented Form method _raw_value() to peek into the data of the request. This works for forms, which have a prefix, too.
class CascadeForm(forms.Form):
parent=forms.ModelChoiceField(Parent.objects.all())
child=forms.ModelChoiceField(Child.objects.none())
def __init__(self, *args, **kwargs):
forms.Form.__init__(self, *args, **kwargs)
parents=Parent.objects.all()
if len(parents)==1:
self.fields['parent'].initial=parents[0].pk
parent_id=self.fields['parent'].initial or self.initial.get('parent') \
or self._raw_value('parent')
if parent_id:
# parent is known. Now I can display the matching children.
children=Child.objects.filter(parent__id=parent_id)
self.fields['children'].queryset=children
if len(children)==1:
self.fields['children'].initial=children[0].pk
jquery Code:
function json_to_select(url, select_selector) {
/*
Fill a select input field with data from a getJSON call
Inspired by: http://stackoverflow.com/questions/1388302/create-option-on-the-fly-with-jquery
*/
$.getJSON(url, function(data) {
var opt=$(select_selector);
var old_val=opt.val();
opt.html('');
$.each(data, function () {
opt.append($('<option/>').val(this.id).text(this.value));
});
opt.val(old_val);
opt.change();
})
}
$(function(){
$('#id_parent').change(function(){
json_to_select('PATH_TO/parent-to-children/?parent=' + $(this).val(), '#id_child');
})
});
Callback Code, which returns JSON:
def parent_to_children(request):
parent=request.GET.get('parent')
ret=[]
if parent:
for children in Child.objects.filter(parent__id=parent):
ret.append(dict(id=child.id, value=unicode(child)))
if len(ret)!=1:
ret.insert(0, dict(id='', value='---'))
return django.http.HttpResponse(simplejson.dumps(ret),
content_type='application/json')
You could set a hidden field to have the real "state" value, then use jQuery to create the <select> list and, on .select(), copy its value to the hidden field. Then, on page load, your jQuery code can fetch the hidden field's value and use it to select the right item in the <select> element after it's populated.
The key concept here is that the State popup menu is a fiction created entirely in jQuery and not part of the Django form. This gives you full control over it, while letting all the other fields work normally.
EDIT: There's another way to do it, but it doesn't use Django's form classes.
In the view:
context = {'state': None, 'countries': Country.objects.all().order_by('name')}
if 'country' in request.POST:
context['country'] = request.POST['country']
context['states'] = State.objects.filter(
country=context['country']).order_by('name')
if 'state' in request.POST:
context['state'] = request.POST['state']
else:
context['states'] = []
context['country'] = None
# ...Set the rest of the Context here...
return render_to_response("addressform.html", context)
Then in the template:
<select name="country" id="select_country">
{% for c in countries %}
<option value="{{ c.val }}"{% ifequal c.val country %} selected="selected"{% endifequal %}>{{ c.name }}</option>
{% endfor %}
</select>
<select name="state" id="select_state">
{% for s in states %}
<option value="{{ s.val }}"{% ifequal s.val state %} selected="selected"{% endifequal %}>{{ s.name }}</option>
{% endfor %}
</select>
You'll also need the usual JavaScript for reloading the states selector when the country is changed.
I haven't tested this, so there are probably a couple holes in it, but it should get the idea across.
So your choices are:
Use a hidden field in the Django form for the real value and have the select menus created client-side via AJAX, or
Ditch Django's Form stuff and initialize the menus yourself.
Create a custom Django form widget, which I haven't done and thus will not comment on. I have no idea if this is doable, but it looks like you'll need a couple Selects in a MultiWidget, the latter being undocumented in the regular docs, so you'll have to read the source.
Based on Mike's suggestion:
// the jQuery
$(function () {
var $country = $('.country');
var $provInput = $('.province');
var $provSelect = $('<select/>').insertBefore($provInput).change(function() {
$provInput.val($provSelect.val());
});
$country.change(function() {
$provSelect.empty().addClass('loading');
$.getJSON('/get-provinces.json', {'country':$(this).val()}, function(provinces) {
$provSelect.removeClass('loading');
for(i in provinces) {
$provSelect.append('<option value="'+provinces[i][0]+'">'+provinces[i][1]+'</option>');
}
$provSelect.val($provInput.val()).trigger('change');
});
}).trigger('change');
});
# the form
country = CharField(initial='CA', widget=Select(choices=COUNTRIES, attrs={'class':'country'}))
province = CharField(initial='BC', widget=HiddenInput(attrs={'class':'province'}))
# the view
def get_provinces(request):
from django.utils import simplejson
data = {
'CA': CA_PROVINCES,
'US': US_STATES
}.get(request.GET.get('country', None), None)
return HttpResponse(simplejson.dumps(data), mimetype='application/json')
Related
I am struggling with validating a form based on damage_type choice field.
I show only one DamageTypeForm in the template (other two are hide by js .hide() function).
Each DamageTypeForm has got some required=True fields, therefore I cannot save the selected form this way:
def createDamage(request):
damage_specify_form = DamageSpecify(request.POST or None)
damage_type_form1 = DamageTypeForm1(request.POST or None)
damage_type_form2 = DamageTypeForm2(request.POST or None)
damage_type_form3 = DamageTypeForm3(request.POST or None)
if request.method == 'POST':
damage_type = request.POST.get('damage_type ')
if damage_type == 'DamageType1':
if damage_type_form1.is_valid():
damage_type_form1.save()
return reverse('damage:type1')
elif damage_type == 'DamageType2':
if damage_type_form2.is_valid():
damage_type_form2.save()
return reverse('damage:type2')
elif damage_type == 'DamageType3':
if damage_type_form3.is_valid():
damage_type_form3.save()
return reverse('damage:type3')
else:
damage_type_form1 = DamageTypeForm1()
damage_type_form2 = DamageTypeForm2()
damage_type_form3 = DamageTypeForm3()
context = {
'damage_specify_form': damage_specify_form,
'damage_type_form1': damage_type_form1,
'damage_type_form2': damage_type_form2,
'damage_type_form3': damage_type_form3,
}
return render(request, 'create_damage.html', context)
How can I get damage_type in View before submitting the form in the template, to save only desired form??
Here are two approaches you could take based on the answer to your previous question. Where it was suggested that you have:
<form>
{% csrf_token %}
{{damage_type_form.as_p}}
<div id="type1">
{{damage_form_1.as_p}}
</div>
<div id="type2">
{{damage_form_2.as_p}}
</div>
<div id="type3">
{{damage_form_3.as_p}}
</div>
<input type="submit" ...>
</form>
I'm not so sure why it was suggested to you that way. But the issue you highlighted is that the other forms (the hidden ones) do have required fields even though their respective forms are hidden.
So one way is to use the novalidate attribute on the form tag:
<form novalidate>
...
</form>
This novalidate is a form-level attribute used to turn off validation for a form, despite the attributes of the inputs it contains (i.e. will override inputs with the required attribute, or that would otherwise fail validation).
NOTE: Do not use novalidate if you simply want to internationalize or otherwise change the content of the default error messages. This can be done with JavaScript.
I personally don't think using the novalidate attribute is a good practice even though it might come in handy for some scenarios.
On the other hand, (my recommendation), you could use JavaScript or Jquery to set/remove the fields required attribute if the respective form is visible or not. For example:
# But let's say you do have separate forms...
<form id="form-1">
...
</form>
<form id="form-2" style="display:none">
...
</form>
# javascript
<script type="text/javascript">
# using jquery here...
$(document).ready(function(){
# Checking for a form's visibility
if($("#form-2").css('display') === 'none'){
# form-2: display is none. Therefore set its field attributes required to false
$('#form-2 input[type="text"]').removeAttr('required');
# or
# $('#form-2 #field_id').removeAttr('required');
}
else{
# form-2: display is not none. Therefore set its field attributes required to true
$('#form-2 input[type="text"]').attr('required','true');
# or
# $('#form-2 #field_id').attr('required','true');
}
});
</script>
Please note that the above JavaScript can be a bit cleaner, even adding some/an event listener to pick up when a form's visibility has changed: to set/remove the required attributes on a specific form.
That's just a basic concept of an approach I'd use.
I searched for so long for a solution to this, but still can't find any. I have a big form in my template, which is actually composed of a bunch of model forms. One field in that big form is not part of a form, but is a single dynamic drop down menu populated from a table called "Institutions" in views.py as such: Institutions.objects.all()
Here is the part from views.py:
def submission_form(request):
institutions = Institution.objects.all()
if request.method == 'POST':
abstractform = AbstractForm(request.POST)
authorform = AuthorForm(request.POST)
# Here I want code: if selected institution is this, then do that
if abstractform.is_valid() and authorform.is_valid()
new_abstract = abstractform.save()
new_author = authorform.save()
else:
return render(request, 'records/submission_form.html', {'abstractform': abstractform, 'authorform': authorform, 'institutions':institutions })
This is the drop down in my template:
<select id="ddlInstititions">
<option value="%">---------</option>
{% for entry in institutions %}
<option value="{{ entry.id }}">{{ entry.name }}</option>
{% endfor %}
</select>
My question is: Is it possible to pass that selected entry.name to the view so I can use it there? If not, what do you recommend doing instead?
Any help would be much appreciated!
In order for any form element to be sent in the POST, you need to have a name attribute. So it should be <select id="ddlInstititions" name="institutions">.
What's passed to the view in the POST is the value attribute of each option element. Currently, you've set that to entry.id, so it's the ID that will be in the POST. You can either use that to look up the Institution object and get the name, or you can change the form so you put entry.name directly in the value attribute.
You can use jQuery's $.ajax() for this.
In your Javascript, you can bind an event handler to #ddlInstititions via
$("#ddlInstitions").on("change", function(){
var selectedValue = $(this).text();
$.ajax({
url : "insititionsSelectHandler/",
type : "GET",
data : {"name" : selectedValue},
dataType : "json",
success : function(){
}
});
});
What this will do is when you make a select event on the dropdown, it will fire this event handler. You will have to define this URL in your `urls.py' like
(r'^/institionsSelectHandler/$', views.insititionsSelectHandler),
and you can get the value inside the view method like
def insititionsSelectHandler(request):
key = request.GET["name"]
...
...
...
#and return your results as a HttpResponse object that contains a dict
return HttpResponse(simplejson.dumps({"success" : "true", "message" : ... }, mimetype = "application/json")
I've been scanning through Django documentation, and Google search results, all afternoon and I'm still somewhat stuck in my attempt to create a dynamic form. I'm hoping I just need someone to nudge me in the right direction :-) I'm just starting to learn Django, so I'm still very much a beginner; however, I'm already an intermediate python user.
What I'm trying to do is create a dynamic form, where the user makes a selection from a drop-down menu, and based on that selection another part of the form will automatically update to display results relevant to the currently selected item, but from another database table.
I'll try and use a simplified version of the models from the Django tutorial to better illustrate what I'm trying to do:
# models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
So lets say I want to have something like a drop-down selection field, populated with the question from each Poll in the database. I also want to have a text-field, which displays the corresponding choices for the currently selected Poll, which will update on-the-fly whenever the user selects a different Pool. I've been able to figure this out by placing a button, and posting information back to the form; However, I'm trying to do this automatically as the user makes a selection. My view sort of looks something like this at the moment:
#view.py
from django import forms
from django.shortcuts import render_to_response
from myapp.models import Poll,Choice
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s" % obj.question
class PollSelectionForm(forms.Form):
polls = MyModelChoiceField( queryset=Poll.objects.all() )
class ChoiceResults(forms.Form):
def __init__(self, newid, *args, **kwargs):
super(ChoiceResults, self).__init__(*args, **kwargs)
self.fields['choice'] = forms.TextField( initial="" )
def main(request):
return render_to_response("myapp/index.html", {
"object": PollSelectionForm(),
"object2": ChoiceResults(),
})
My template is very simple, just something like
{{ object }}
{{ object2 }}
I'm sure the way I'm going about creating the forms is probably not the best either, so feel free to criticize that as well :-) As I mentioned, I've read solutions involving reposting the form, but I want this to happen on-the-fly... if I can repost transparently then that would be fine I guess. I've also seen libraries that will let you dynamically create forms, but that just seems like overkill.
Here is one approach - Django/jQuery Cascading Select Boxes?
You can create a new view that just renders json to a string,
and then trigger an event when you're done selecting from the first list which loads the data dynamically from that json.
I do a similar thing here, populating a form based on a selection in a drop down. Maybe this helps you.
Here is the model of the values used to pre-populate the form:
class OpmerkingenGebrek(models.Model):
opmerking = models.CharField(max_length=255)
advies = models.CharField(max_length=255)
urgentiecodering = models.CharField(max_length=2, choices=URGENTIE_CHOICES_2011)
bepaling = models.CharField(max_length=155,blank=True,null=True)
aard = models.CharField(max_length=3, choices=AARD_CHOICES)
The view that manages the form:
def manage_component(request,project_id,.....):
# get values for pre-populate
og = OpmerkingenGebrek.objects.all()
.........
formset = ComponentForm(request.POST,request.FILES)
.........
)))
return render_to_response(template, {
'formset':formset,
........
'og':og,
},context_instance=RequestContext(request))
The html the renders the form
{% extends "base.html" %}
{% block extra_js %}
<script type="text/javascript" src="/media/js/limitText.js"></script>
<script type="text/javascript" src="/media/js/getValueOpmerking.js"></script>
{% endblock %}
<form enctype="multipart/form-data" method="post" action="">
{{ formset.as_table }}
</form>
<p>Choose default values:</p>
<select id="default" onChange="getValue(this)">
{% for i in og %}
<option value="{{ i.opmerking }} | {{ i.advies }} | {{ i.urgentiecodering }} |
{{ i.aard }} | {{ i.bepaling }}">{{ i.opmerking }}</option>
{% endfor %}
</select>
The javascript that pre-populates the form:
function getValue(sel)
{
//get values
var opm = sel.options[sel.selectedIndex].value;
//split string to parts
var parts = opm.split("|");
// autofill form
var opmerking = document.getElementById("id_opmerking");
opmerking.value = parts[0];
var aanbeveling = document.getElementById("id_aanbeveling");
aanbeveling.value = parts[1];
var opt = document.getElementById("id_urgentie");
var urgentie = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[2].split(' ').join('')){
opt.selectedIndex = i;
}};
var opt = document.getElementById("id_aard");
var aard = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[3].split(' ').join('')){
opt.selectedIndex = i;
}};
var bepaling = document.getElementById("id_bepaling");
bepaling.value = parts[4];
};
I've been scanning through Django documentation, and Google search results, all afternoon and I'm still somewhat stuck in my attempt to create a dynamic form. I'm hoping I just need someone to nudge me in the right direction :-) I'm just starting to learn Django, so I'm still very much a beginner; however, I'm already an intermediate python user.
What I'm trying to do is create a dynamic form, where the user makes a selection from a drop-down menu, and based on that selection another part of the form will automatically update to display results relevant to the currently selected item, but from another database table.
I'll try and use a simplified version of the models from the Django tutorial to better illustrate what I'm trying to do:
# models.py
from django.db import models
class Poll(models.Model):
question = models.CharField(max_length=200)
class Choice(models.Model):
poll = models.ForeignKey(Poll)
choice = models.CharField(max_length=200)
So lets say I want to have something like a drop-down selection field, populated with the question from each Poll in the database. I also want to have a text-field, which displays the corresponding choices for the currently selected Poll, which will update on-the-fly whenever the user selects a different Pool. I've been able to figure this out by placing a button, and posting information back to the form; However, I'm trying to do this automatically as the user makes a selection. My view sort of looks something like this at the moment:
#view.py
from django import forms
from django.shortcuts import render_to_response
from myapp.models import Poll,Choice
class MyModelChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s" % obj.question
class PollSelectionForm(forms.Form):
polls = MyModelChoiceField( queryset=Poll.objects.all() )
class ChoiceResults(forms.Form):
def __init__(self, newid, *args, **kwargs):
super(ChoiceResults, self).__init__(*args, **kwargs)
self.fields['choice'] = forms.TextField( initial="" )
def main(request):
return render_to_response("myapp/index.html", {
"object": PollSelectionForm(),
"object2": ChoiceResults(),
})
My template is very simple, just something like
{{ object }}
{{ object2 }}
I'm sure the way I'm going about creating the forms is probably not the best either, so feel free to criticize that as well :-) As I mentioned, I've read solutions involving reposting the form, but I want this to happen on-the-fly... if I can repost transparently then that would be fine I guess. I've also seen libraries that will let you dynamically create forms, but that just seems like overkill.
Here is one approach - Django/jQuery Cascading Select Boxes?
You can create a new view that just renders json to a string,
and then trigger an event when you're done selecting from the first list which loads the data dynamically from that json.
I do a similar thing here, populating a form based on a selection in a drop down. Maybe this helps you.
Here is the model of the values used to pre-populate the form:
class OpmerkingenGebrek(models.Model):
opmerking = models.CharField(max_length=255)
advies = models.CharField(max_length=255)
urgentiecodering = models.CharField(max_length=2, choices=URGENTIE_CHOICES_2011)
bepaling = models.CharField(max_length=155,blank=True,null=True)
aard = models.CharField(max_length=3, choices=AARD_CHOICES)
The view that manages the form:
def manage_component(request,project_id,.....):
# get values for pre-populate
og = OpmerkingenGebrek.objects.all()
.........
formset = ComponentForm(request.POST,request.FILES)
.........
)))
return render_to_response(template, {
'formset':formset,
........
'og':og,
},context_instance=RequestContext(request))
The html the renders the form
{% extends "base.html" %}
{% block extra_js %}
<script type="text/javascript" src="/media/js/limitText.js"></script>
<script type="text/javascript" src="/media/js/getValueOpmerking.js"></script>
{% endblock %}
<form enctype="multipart/form-data" method="post" action="">
{{ formset.as_table }}
</form>
<p>Choose default values:</p>
<select id="default" onChange="getValue(this)">
{% for i in og %}
<option value="{{ i.opmerking }} | {{ i.advies }} | {{ i.urgentiecodering }} |
{{ i.aard }} | {{ i.bepaling }}">{{ i.opmerking }}</option>
{% endfor %}
</select>
The javascript that pre-populates the form:
function getValue(sel)
{
//get values
var opm = sel.options[sel.selectedIndex].value;
//split string to parts
var parts = opm.split("|");
// autofill form
var opmerking = document.getElementById("id_opmerking");
opmerking.value = parts[0];
var aanbeveling = document.getElementById("id_aanbeveling");
aanbeveling.value = parts[1];
var opt = document.getElementById("id_urgentie");
var urgentie = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[2].split(' ').join('')){
opt.selectedIndex = i;
}};
var opt = document.getElementById("id_aard");
var aard = opt.selectedIndex;
for(var i=0;i<opt.length;i++){
if(opt.options[i].value == parts[3].split(' ').join('')){
opt.selectedIndex = i;
}};
var bepaling = document.getElementById("id_bepaling");
bepaling.value = parts[4];
};
I would like to have a custom snippet of html form code that takes allows the user to select a 'training' that is then used as a query parameter to a django-admin model filter for 'participants'.
I've successfully created the filter on the modeladmin:
class ParticipantAdmin(RestrictedModelAdmin):
list_filter = ('training__name',)
It's probably worth noting that RestrictedModleAdmin is a subclass of ModelAdmin that provides row-level security for the model; logged in users should only see rows they own.
Thus, urls using this filter look something like this when just using that admin interface:
/admin/core/participant/?training__name=Menno+Ropes
All that works great. Now I think I should be able to create a very simple form that allows selecting a valid 'training' and submitting that to /admin/core/participant/ as a GET.
<form method="GET" action="/admin/core/participant/">{% csrf_token %}
<ol>
<li>Select your training:
<select name='training__name'>
<option value=''>—</option>
{% for training in trainings %}
<option value='{{ training.name }}'>{{ training }}</option>
{% endfor %}
</select>
</li>
<li>See participants for that training.
<input type='submit' name='submit' value='Submit' /></li>
</ol>
</form>
This last bit doesn't see to work. Some magic foo in the django innards seems to always mangle the submission to be:
/admin/core/participant/?e=1
This obviously doesn't select the appropriate filter value and thus shows an unfiltered list of 'participants'.
What's going on? What can I do to get it to allow my GET parameter to pass through to the admin model?
Thanks in advance.
PS) Django 1.3+
The problem is that you have a name attribute in your <input type="submit">, causing an extra GET parameter: submit which is throwing the invalid lookup error and thus e=1
Remove the name attribute and you're good to go.
I did a little experiment to confirm since I thought it odd that the server might somehow treat a browser GET differently.
It's a little bit tricky, but it works for me:
def changelist_view(self, request, bill_id, extra_context=None):
"""queryset is an extra parameter"""
req = request.GET.copy()
if 'queryset' in req:
queryset = req.pop('queryset')[0]
else:
queryset = request.META['HTTP_REFERER'].split('queryset=')[1]
url = "/admin/billing/invoice/%s/select_to_move/?%s&queryset=%s" % (bill_id, request.GET.urlencode(), queryset)
return HttpResponseRedirect(url)
request.GET = req
# Do stuff with queryset.
return super(MyAdminClass, self).changelist_view(request, context)