Django forms ChoiceField: how to add data attributes to input radios? - django

I have a form with a choicefield:
class CheckoutForm(forms.Form):
shipping_method = forms.ChoiceField(widget=forms.RadioSelect)
How can I add data attributes to every choice? Something like:
<ul id="id_shipping_method">
<li>
<label for="id_shipping_method_0">
<input class="form-control" id="id_shipping_method_0" name="shipping_method" type="radio" value="C" data-method="courier"> Express courier</label></li>
<li>
<label for="id_shipping_method_1">
<input checked="checked" class="form-control" id="id_shipping_method_1" name="shipping_method" type="radio" value="yy" data-method="shop">In shop</label></li>
</ul>

Edit: Reread the question, updated a few things
A bit messy, but this should get you on the right track. You need to override some of the rendering components of the RadioSelect.
from django import forms
CHOICES = (('C','Express courier'),('yy','In shop'), ('h','By hand'))
class MyRadioChoiceInput(forms.widgets.RadioChoiceInput):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
method = {'C': 'courier', 'yy': 'shop', 'h': 'hand'}.get(self.choice_value)
self.attrs['data-method'] = method
class MyRadioFieldRenderer(forms.widgets.ChoiceFieldRenderer):
choice_input_class = MyRadioChoiceInput
class MyRadioSelect(forms.RadioSelect):
renderer = MyRadioFieldRenderer
class CheckoutForm(forms.Form):
shipping_method = forms.ChoiceField(choices=CHOICES, widget=MyRadioSelect(attrs={'class': 'form-control'}))
Example:
a = CheckoutForm()
for x in a:
print(x)
Result:
<ul id="id_shipping_method">
<li><label for="id_shipping_method_0"><input class="form-control" data-method="courier" id="id_shipping_method_0" name="shipping_method" type="radio" value="C" /> Express courier</label></li>
<li><label for="id_shipping_method_1"><input class="form-control" data-method="shop" id="id_shipping_method_1" name="shipping_method" type="radio" value="yy" /> In shop</label></li>
<li><label for="id_shipping_method_2"><input class="form-control" data-method="hand" id="id_shipping_method_2" name="shipping_method" type="radio" value="h" /> By hand</label></li>
</ul>

Related

Passing a variable from a form input to a flask URL

I have a dummy project in Flask, which consists in a web form, where you fill in 3 cities, which are then printed upon submition.
This is my init.py file:
#app.route('/average_temperatures/<city1>/<city2>/<city3>', methods=['POST', 'GET'])
def results_temperature(city1, city2, city3):
return "The cities are: {}, {}, {}".format(city1, city2, city3)
The function works, but I am unable to pass the variables from the form straight into the function, as parameters.
edit:
My goal is to have the city variables as part of the URL in the clean form of /city1/city2/city3.
This is the form:
<div class="input-group">
<form action="{{ url_for('results_temperature', city1=city1, city2=city2, city3=city3) }}" method="POST">
<input type="text" class="form-control" placeholder="City 1" name="city1"></input>
<input type="text" class="form-control" placeholder="City 2" name="city2"></input>
<input type="text" class="form-control" placeholder="City 3" name="city3"></input>
<div class="row" style="margin-bottom: 20px;"></div>
Filling out the form results in a URL
http://example.com/average_temperatures///
So I evidently fail to pass the form fields in the form action part.
Any hits are appreciated, cheers.
You should get the cities from the request.form variable.
#app.route('/average_temperatures', methods=['POST', 'GET'])
def results_temperature():
return "The cities are: {}, {}, {}".format(request.form['city1'], request.form['city2'], request.form['city3'])
In your HTML form:
<form action="{{ url_for('results_temperature') }}" method="POST">
<input type="text" class="form-control" placeholder="City 1" name="city1"></input>
<input type="text" class="form-control" placeholder="City 2" name="city2"></input>
<input type="text" class="form-control" placeholder="City 3" name="city3"></input>
</form>

render a individual field in a form-inline using 'as_crispy_field'

I need to render individual fields from my Form so I use the filter |as_crispy_field from crispy-forms and use bootstrap 3 for the style but I need to do it in a form-inline like the first example here bootstrap example page so I tried to do it like this:
template.html
<form class="form-inline">{% csrf_token %}
{{ form.name|as_crispy_field }}
</form>
But the label shows above the TextInput field and not in-line as I need. How can I do this using |as_crispy_field?
EDIT: Here is the HTML after the render with crispy
<form class="form-inline"><input type='hidden' name='csrfmiddlewaretoken' value='****...' />
<div id="div_id_name" class="form-group">
<label for="id_name" class="control-label requiredField">
Name<span class="asteriskField">*</span>
</label>
<div class="controls ">
<input class="form-control textinput textInput form-control" id="id_name" maxlength="100" name="name" type="text" />
</div>
</div>
</form>
The one you linked to, with the labels on the left of the fields, is called "form-horizontal" in Crispy. It is explained here. You need to set these helper properties
helper.form_class = 'form-horizontal'
helper.label_class = 'col-lg-2'
helper.field_class = 'col-lg-8'
Actual inline forms, with the label displayed inside the fields are right below on the same page. You only need to set two helper properties
helper.form_class = 'form-inline'
helper.field_template = 'bootstrap3/layout/inline_field.html'
That should do it, if I understood your question correctly.

How can I access data sent in a post request in Django?

I have a form that is supposed to create a new 'Quote' record in Django. A 'Quote' requires a BookID for a foreign key.
This is my form
<form method="POST" action="{% url 'quotes:createQuote' %}">
{% csrf_token %}
<section>
<label for="q_text">Quote Text</label>
<input type="text" name="text" id="q_text" placeholder="Enter a Quote" style="padding-left:3px"> <br>
<label for="q_book">Book ID</label>
<input type="text" name="bookID" id="q_book" placeholder="Enter Book ID" style="padding-left:3px"> <br>
<label for="q_disp">Display Quote Now?</label>
<input type="radio" name="display" id="q_disp" value="True"> True
<input type="radio" name="display" value ="False">False <br>
<button value="submit">Submit</button>
</section>
</form>
And this is the method that it is targeting
def createQuote(request):
#b = get_object_or_404(Book, pk=request.bookID)
return HttpResponseRedirect(reverse('quotes:index'))
Somewhere in that request argument I assume there is some sort of field that contains the bookID the user will pass in on the form. How do I get at that information?
Bonus points for anyone who can tell me some way I can visualise data like I might with console.log(some.collection) in Javascript
if request.method == "POST":
book_id = request.POST['book_id']
Assuming you're sure it's in there. Otherwise you'll need to verify/provide a default value like you would for a normal python dictionary.
As for visualising the data, do you mean printing it to the console? In which case if you're running the django runserver you can just do print some_data. If you want it formatted a little nicer, you can use pretty print:
import pprint
pp = pprint.PrettyPrinter()
pp.pprint(some_data)

Customizing Django forms with RadioSelect widget

So I'm using jQuery UI to skin the radio buttons but I can't get Django to render my form the way it has to be done.
I need to have this structure:
<table>
<tr>
<td><label for="notify_new_friends">Notify when new friends join</label></td>
<td class="radio">
<input type="radio" name="notify_new_friends" id="notify_new_friends_immediately" value="1" checked="checked"/><label for="notify_new_friends_immediately">Immediately</label>
<input type="radio" name="notify_new_friends" id="notify_new_friends_never" value="0"/><label for="notify_new_friends_never">Never</label>
</td>
</tr>
</table>
So to summarize that I need the radio buttons within a class (radio) where they have an input and a label for.
When I render the form in my template with {{ profile_form.notify_new_friends }} I get the following:
<ul>
<li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li>
<li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li>
</ul>
Which is exactly what I want except for the list-part. So I tried looping over it which gives me the labels formatted differently:
{% for item in profile_form.notify_new_friends %}
{{ item }}
{% endfor %}
which gives me:
<label><input type="radio" name="notify_new_friends" value="0" /> Immediately</label>
<label><input type="radio" name="notify_new_friends" value="1" /> Never</label>
So the problem here is that it stops using label for and starts using just label to wrapp it all with.
I also tried doing something like this, but then the label and label_tag don't render anything.
{{ profile_form.notify_new_friends.0 }}
{{ profile_form.notify_new_friends.0.label_tag }}
{{ profile_form.notify_new_friends.0.label }}
So does anyone know how I can render this properly!?
FYI, this is my forms.py:
self.fields['notify_new_friends'] = forms.ChoiceField(label='Notify when new friends join', widget=forms.RadioSelect, choices=NOTIFICATION_CHOICES)
In my code I discovered that changing the widget from
forms.RadioSelect
to
forms.RadioSelect(attrs={'id': 'value'})
magically causes the resulting tag value to include the id attribute with the index of the item appended. If you use
{% for radio in form.foo %}
<li>
{{ radio }}
</li>
{% endfor %}
in the form you get a label wrapped around an input. If you want the more conventional input followed by label, you need to do this:
{% for radio in form.value %}
<li>
{{ radio.tag }}
<label for="value_{{ forloop.counter0 }}">{{ radio.choice_label }}</label>
</li>
{% endfor %}
Unfortunately this is more complicated than it should be, it seems you need to override at least 2 classes: RadioRenderer and RadioInput. The following should help you get started but you might need to tweak it a little.
First create a custom radio button input widget. The only purpose of us overriding the render method is to get rid of annoying structure Django enforces (<label><input /></label>) where instead we want ours (<label /><input />):
class CustomRadioInput(RadioInput):
def render(self, name=None, value=None, attrs=None, choices=()):
name = name or self.name
value = value or self.value
attrs = attrs or self.attrs
if 'id' in self.attrs:
label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
else:
label_for = ''
choice_label = conditional_escape(force_unicode(self.choice_label))
return mark_safe(u'%s<label%s>%s</label>' % (self.tag(), label_for, choice_label))
Now we need to override RadioRenderer in order to:
Force it to use our custom radio input widget
Remove <li> wraping every single input field and <ul> wrapping all input fields:
Something along these lines should do:
class RadioCustomRenderer(RadioFieldRenderer):
def __iter__(self):
for i, choice in enumerate(self.choices):
yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)
def __getitem__(self, idx):
choice = self.choices[idx]
return CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
def render(self):
return mark_safe(u'%s' % u'\n'.join([u'%s' % force_unicode(w) for w in self]))
Finally instruct Django to use custom renderer
notify_new_friends = forms.ChoiceField(label='Notify when new friends join', widget=forms.RadioSelect(renderer=RadioCustomRenderer), choices=NOTIFICATION_CHOICES)
Please bear in mind: This now outputs radio buttons together with encompassing <td> hence you need to build a table around it in your template, something along these lines:
<table>
<tr>
<td><label for="{{field.auto_id}}">{{field.label}}</label></td>
<td>{{ field.errors }} {{field}}</td>
</tr>
</table>
If anyone stumble upon this problem and just want to render the radio button without ul: they should follow this link.
https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#selector-widgets
Example below.
{% for radio in myform.beatles %}
<div class="myradio">
{{ radio }}
</div>
{% endfor %}
Since it doesn't seem to be a good way to do this I chose to rearrange the generated code using jQuery.
// First remove the ul and li tags
$('.radio ul').contents().unwrap();
$('.radio li').contents().unwrap();
// Then move the input to outside of the label
$('.radio > label > input').each(function() {
$(this).parent().before(this);
});
// Then apply the jQuery UI buttonset
$( ".radio" ).buttonset();
This made it go from:
<ul>
<li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li>
<li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li>
</ul>
to:
<input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /><label for="id_notify_new_friends_0"> Immediately</label></li>
<input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /><label for="id_notify_new_friends_1"> Never</label></li>
and my jQuery UI styling works fine.
Try like this , I got it..
from django.forms.widgets import RadioFieldRenderer
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
class RadioCustomRenderer( RadioFieldRenderer ):
def render( self ):
return mark_safe(u'%s' % u'\n'.join([u'%s' % force_unicode(w) for w in self]))
in form
widgets = {
'validity': forms.RadioSelect(renderer=RadioCustomRenderer),
}

Django formset apparently only printing first form in template (because of invalid markup)

[Edit: See my answer below - the origin of this issue is invalid markup, and browsers working very hard to hide that. ]
I have a formset which definitely should contain two forms, but for whatever reason, I am only getting one form printed in the template.
This is the template line:
<tr id="existing_docs_row"><td colspan="2">{{ existing_articles.management_form }}{% for f in existing_articles %}<div>{{ f }}</div>{% endfor %}</td></tr>
I get the exact same behaviour (less div tags) with:
<tr id="existing_docs_row"><td colspan="2">{{ existing_articles }}}</td></tr>
The management form and first form are created, but not the second. This is what I get in my browser:
<input type="hidden" id="id_form-TOTAL_FORMS" value="2" name="form-TOTAL_FORMS"><input type="hidden" id="id_form-INITIAL_FORMS" value="2" name="form-INITIAL_FORMS"><input type="hidden" id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS"><div><div class="selected_row " id="selected_row"><span class="formlabel"></span><ul>
<li><label for="id_form-0-selected_0"><input type="radio" name="form-0-selected" value="True" id="id_form-0-selected_0"> </label></li>
</ul></div>
<div class="original_filename_row " id="original_filename_row"><span class="formlabel"><span id="for-id_form-0-original_filename-">Original filename:</span></span><div id="id_form-0-original_filename" name="form-0-original_filename">FakeExampleCompanyName.docx</div></div>
<div class="tags_row " id="tags_row"><span class="formlabel"><span id="for-id_form-0-tags-">Tags:</span></span><div id="id_form-0-tags" name="form-0-tags" class="tagarea"><span class="tagitem">England and Wales</span> <span class="tagitem">Private company limited by shares</span> <span class="tagitem">Model articles with amendments</span></div></div>
Breaking in the view, and printing the formset shows that it contains two forms (existing_template_formset is the name of the formset inside the view):
>>> print existing_template_formset <input type="hidden" name="form-TOTAL_FORMS" value="2" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<div id="selected_row" class="selected_row "> <span class="formlabel"></span><ul> <li><label for="id_form-0-selected_0"><input type="radio" id="id_form-0-selected_0" value="True" name="form-0-selected" /> </label></li> </ul></div> <div id="original_filename_row" class="original_filename_row "><span class="formlabel"><span id="for-id_form-0-original_filename-">Original filename:</span></span><div name="form-0-original_filename" id="id_form-0-original_filename">FakeExampleCompanyName.docx</div></div> <div id="tags_row" class="tags_row "><span class="formlabel"><span id="for-id_form-0-tags-">Tags:</span></span><div class="tagarea" name="form-0-tags" id="id_form-0-tags" ><span class="tagitem" >England and Wales</span> <span class="tagitem" >Private company limited by shares</span> <span class="tagitem" >Model articles with amendments</span></div></div> <tr><th></th><td><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>
<div id="selected_row" class="selected_row "><span class="formlabel"></span><ul> <li><label for="id_form-1-selected_0"><input type="radio" id="id_form-1-selected_0" value="True" name="form-1-selected" /> </label></li> </ul></div> <div id="original_filename_row" class="original_filename_row "><span class="formlabel"><span id="for-id_form-1-original_filename-">Original filename:</span></span><div name="form-1-original_filename" id="id_form-1-original_filename" >FakeExampleCompanyName.docx</div></div> <div id="tags_row" class="tags_row "><span class="formlabel"><span id="for-id_form-1-tags-">Tags:</span></span><div class="tagarea" name="form-1-tags" id="id_form-1-tags" ></div></div> <tr><th></th><td><input type="hidden" name="form-1-id" id="id_form-1-id" /></td></tr>
>>> len(existing_template_formset) 2
As you can see, in both cases, the total number of forms in the formset is 2 (as evidenced in the management form), but the second one is simply not generated.
Has anyone come across this before? How do I fix this?
I'm using django 1.3.1 on python 2.7.2 on windows.
For completeness, here is the code which creates the formset:
class ExistingTemplateFormset(modelformset_factory(ArticlesTemplate, extra = 0, form=ExistingTemplateForm)):
def __init__(self, *args, **kwargs):
super(ExistingTemplateFormset, self).__init__(*args, **kwargs)
for x in self:
x.fields['id'].widget = forms.HiddenInput()
x.fields['original_filename'].editable = False
x.fields['original_filename'].widget = SpanWidget(tag = u'div')
x.fields['tags'].widget= TagArea()
x.fields['tags'].help_text = u''
(TagArea and SpanWidget exist)
In the view:
existing_template_formset = ExistingTemplateFormset(queryset = the_organisation.get_template_articles())
Sharp-eyed readers (which, it turns out, does not include me, hence this problem) will note that my output includes at the end of each form:
`<tr><th></th><td><input type="hidden" name="form-0-id" id="id_form-0-id" /></td></tr>`
Now, when that is substituted into <tr id="existing_docs_row"><td colspan="2">{{ existing_articles.management_form }}{% for f in existing_articles %}<div>{{ f }}</div>{% endfor %}</td></tr> that leads to invalid markup (a tr inside a tr!).
So, it turns out that the template was generating the second form, but the browser's error recovery methods (in chrome, disregarding a lot of the invalid markup; in firefox, floating the second form to elsewhere in the DOM) created the appearance that the second form wasn't being generated.
To summarise: just examining the DOM mislead me. Try to force your browser to choke on errors, and look at the raw markup.