django form radio input layout - python

What is the "djangoy" way to approach this problem:
In my form class, I have a forms.ChoiceField whose widget is a forms.RadioSelect widget, one of whose choices needs to be presented with an inline text input (which is also a field in the form). I'm using custom validation to ignore the text field when its radio choice is not selected. When rendered, I want it to appear like below:
<ul>
<li><label for="id_rad_0"><input type="radio" id="id_rad_0" value="none" name="rad" /> No Textbox</label></li>
<li><label for="id_rad_1"><input type="radio" id="id_rad_1" value="one" name="rad" /> One Textbox: <input type="text" name="bar" id="id_bar" /></label></li>
</ul>
However, I can't simply produce this in my template, because the radio choices are not exposed. I can't see a way to do this without tightly coupling my form to my template, or alternately, putting all of the presentation logic in the form class. What is the right way to solve this problem?
edit
I realize that the above might just be an obscure problem, but I'm not sure exactly what other information I can provide in order to inspire someone to help me with this. I'm a much better backend programmer than web designer, and I'm on this project alone, so maybe it's a lack of education - is what I described simply poor design? Should I just be designing this a different way? I'm really open to any suggestion here that will help me move past this.
edit 2
Per request, the current code, shortened to save sanity, names changed to protect the innocent:
# forms.py
from myapp.models import RatherComplicatedModel
from django import forms
class RatherComplicatedForm(forms.ModelForm):
#various and sundry code...
RADIO_CHOICES = (
('none', "No Textbox"),
('one', "One Textbox: "),
)
# although I've abbreviated the model, 'rad' does not appear in the model;
# it merely provides input to the un-provided clean function
rad = forms.ChoiceField(widget=forms.RadioSelect(),choices=RADIO_CHOICES)
class Meta:
model = RatherComplicatedModel
-
# models.py
from django.db import models
class RatherComplicatedModel(models.Model):
#some other stuff...
bar = models.IntegerField(blank=True,null=True)

If I understand your problem correctly, you can access choices tuple in template:
<ul>
{# Assuming {{ field }} here is {{ form.rad }} #}
{% for choice in field.field.choices %}
<li>
<label for="id_{{ field.html_name }}_{{ forloop.counter0 }}">
<input type="radio"
id="id_{{ field.html_name }}_{{ forloop.counter0 }}"
value="{{ choice.0 }}"
name="{{ field.html_name }}" />
{{ choice.1 }}
{% if choice.0 == 'one' %}
{# Necessary field here #}
{{ form.bar }}
{% else %}
No Textbox
{% endif %}
</label>
</li>
{% endfor %}
</ul>

Anton's answer worked, and was a decent answer for a while there - but unfortunately it became unmaintainable. So, taking a cue from a diff attached to django ticket #9230, I just monkey patched django.forms.forms.BoundField
from django import forms
def MonkeyPatchDjangoFormsBoundField():
def prepare_widget_render(self, widget=None, attrs=None, only_initial=False):
"""
Prepare the data needed for the widget rendering.
"""
if not widget:
widget = self.field.widget
attrs = attrs or {}
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
if not only_initial:
attrs['id'] = auto_id
else:
attrs['id'] = self.html_initial_id
if not only_initial:
name = self.html_name
else:
name = self.html_initial_name
return widget, name, attrs
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
Renders the field by rendering the passed widget, adding any HTML
attributes passed as attrs. If no widget is specified, then the
field's default widget will be used.
"""
widget, name, attrs = self.prepare_widget_render(widget, attrs, only_initial)
return widget.render(name, self.value(), attrs=attrs)
def __iter__(self):
"""
Check if current widget has a renderer and iterate renderer.
"""
widget, name, attrs = self.prepare_widget_render()
if not hasattr(widget, 'get_renderer'):
raise Exception, "Can not iterate over widget '%s'" % widget.__class__.__name__
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
for entry in renderer:
yield entry
def __getitem__(self,idx):
"""
Tries to use current widget's renderer, and then check attribute.
"""
widget, name, attrs = self.prepare_widget_render()
try:
renderer = widget.get_renderer(name, self.value(), attrs=attrs)
return renderer[idx]
except Exception:
return getattr(self,idx)
forms.forms.BoundField.prepare_widget_render = prepare_widget_render
forms.forms.BoundField.as_widget = as_widget
forms.forms.BoundField.__iter__ = __iter__
forms.forms.BoundField.__getitem__ = __getitem__
This allowed me to be able to access the radio inputs directly, by using {{ form.field.0.tag }}, or through iteration - {% for radio in form.field %} {{ radio.tag }} {% endfor %}. Much easier to take care of!

Choices should be in the Model:
class RatherComplicatedModel(models.Model):
BAR_CHOICES = (
(0, "No Textbox"),
(1, "One Textbox: "),
)
#some other stuff...
bar = models.IntegerField(blank=True, null=True, choices=BAR_CHOICES)
Then just:
class RatherComplicatedForm(forms.ModelForm):
#various and sundry code...
bar = forms.ChoiceField(widget=forms.RadioSelect(),
choices=RatherComplicatedModel.BAR_CHOICES)
class Meta:
model = RatherComplicatedModel

I would do this by subclassing RadioFieldRenderer and attaching it to a custom widget:
# forms.py
from django import forms
from django.forms.widgets import RadioSelect, RadioFieldRenderer
from django.template.loader import render_to_string
from myapp.models import RatherComplicatedModel
class MyRadioFieldRenderer(RadioFieldRenderer):
def render(self):
return render_to_string(
'my_radio_widget.html',
{'field': self})
class MyRadioSelect(RadioSelect):
renderer = MyRadioFieldRenderer
class RatherComplicatedForm(forms.ModelForm):
RADIO_CHOICES = (
('none', "No Textbox"),
('one', "One Textbox: "),
)
rad = forms.ChoiceField(widget=MyRadioSelect(),choices=RADIO_CHOICES)
class Meta:
model = RatherComplicatedModel
Then the template:
#my_radio_widget.html
<ul>
{% for choice in field %}
<li>
<label for="id_{{ field.name }}_{{ forloop.counter0 }}">
<input type="radio"
name="{{ field.name }}"
value="{{ choice.choice_value }}"
id="id_{{ field.name }}_{{ forloop.counter0 }}"
{% if field.value == choice.choice_value %}
checked='checked'
{% endif %}/>
{{ choice.choice_label }}
</label>
</li>
{% endfor %}
</ul>

Related

Can you use a regular expression in a Django template conditional?

Is it possible to determine if a template variable in a Django template satisfies a regular expression? In the following template, I want to set the CSS class for the paragraph tag that contains the help text based on whether or not the help text for that field satisfies a regular expression. Here is the template with some pseudocode thrown in:
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
{% if field.help_text|lower (BEGINS WITH SOME STRING) %} # Pseudocode
<p class="select-help-text">{{ field.help_text|safe }}</p>
{% else %}
<p class="help-text">{{ field.help_text|safe }}</p>
{% endif %}
{% endif %}
</div>
{% endfor %}
For example, if the help_text as defined in the associated form starts with the text string "Hold down Ctrl", then the CSS class should be set to select-help-text, otherwise it should just be set to help-text.
I understand that Django regular expressions are based on Python regexes, but Python regex evaluations always seems to be done using the re module which isn't accessible in a Django template. I also looked through the Django documentation but couldn't find a way to do this.
UPDATE
I still can't get this code to work.
Melvyn, who answered below, is technically correct. You should avoid putting conditional logic in Django templates. To that end, I changed my template per the Django documentation:
{% for field in form.visible_fields %}
<div class="form-group">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help-text">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
I then added an __init__ method to my forms ModelForm class that looks at the label for the form fields that shouldn't have their help text displayed and sets help_text to a "falsey" value so the condition in the template will fail:
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
if visible.field.label == 'foo' or visible.field.label == 'bar' or visible.field.label == 'baz':
visible.field.help_text = False
However, I'm still seeing the help text whether I set help_text to False or None or an empty string. Is this some type of timing issue or have I made a mistake that I'm just not seeing?
DTL (Django Template Language) is not meant to program. In fact, I wouldn't even write a template tag for this. In your view (or form, or field or widget), you have all the power to change the help text to a 2-tuple or dict with a label, so why don't ya ;)
Perhaps the best approach is in the field or widget and just add the desired class to the widget based on the help text.
Overriding model form fields
So, to provide the answer for the update, this is the trimmed down example I used:
models.py
from django.db import models
class Sensor(models.Model):
color = models.CharField(
max_length=20, verbose_name="visible", help_text="Colors are visible"
)
sound = models.CharField(
max_length=20, verbose_name="audible", help_text="Sounds are audible"
)
forms.py
class BasicModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
normalized = visible.label.lower()
if "audible" == normalized:
visible.help_text = ""
class Meta:
model = Sensor
fields = "__all__"
tests.py
from django.template import Template, Context
class FormTest(TestCase):
def test_modelform(self):
form = BasicModelForm()
self.assertEqual(form["sound"].help_text, "")
def test_template(self):
form = BasicModelForm()
context = Context({"form": form})
template = Template("{{ form.sound.help_text }} | {{form.color.help_text}}")
actual = template.render(context)
self.assertEqual(""" | Colors are visible""", actual)
It failed before normalizing the label value. I used the test to have PyCharm break at the assignment, but it never got to the assignment, so then I braked at the if statement to see "Audible" instead of "audible".

I want to access struct block default ID in its template

I want to save stream field ID into it's template.
In short, in text_question.html I am giving id = {{ self.id }} but that return Nothing.
I want this because in question.html file I want it to compare with {{
field.id }} which return stream field ID
In another word, I want to store {{ field.id }}'s value in id field of text_question.html
models.py
class TextQuestionBlock(blocks.StructBlock):
"""Text Question"""
question = blocks.CharBlock(required=True, help_text="Add your Question")
is_save = blocks.BooleanBlock(label="Want to save this field ?", required=False)
is_email = blocks.BooleanBlock(label="Want to get this field as an email ?", required=False)
class Meta: # noqa
template = "question/question_field/text_question.html"
icon = "edit"
label = "Text Question"
#register_setting(icon='fa-commenting')
class QuestionSettings(BaseSetting):
body = StreamField([
("text_question", TextQuestionBlock()),
], verbose_name='Question', blank=True)
panels = [
StreamFieldPanel('body')
]
class Meta:
verbose_name_plural = 'Question'
verbose_name = 'Questions'
text_question.html
{% load tag_library %}
<input issave="{{self.is_save}}" isemail="{{ self.is_email }}" class="text_question" type="text" name="{{ self.question|to_name }}" id="{{ self.id }}" data-conv-question="{{ self.question }}"
question.html
<form name="question_form" action="" method="post" class="hidden">
<div id="unique_id"></div>
{% for field in question.body %}
{{ field.id }}
{% endfor %}
<input type="text" data-conv-question="test">
</form>
Thank You!!!
The ID is not a built-in property of the block value - rather, it's a mechanism used by the StreamField container to keep track of its contents. It's not always meaningful for a block value to have an ID property: for example, the value of a CharBlock is a string, and you can't really have an .id property on a string. Similarly, child blocks of StructBlock won't be given one.
As a result, the id is not automatically available on a block's template - if you want it, you need to pass it explicitly from the calling template, via the {% include_block %} template tag. For example:
{% for field in question.body %}
{% if field.block_type == 'text_question' %}
{% include_block field with block_id=field.id %}
{% endif %}
{% endfor %}
This will make the ID available on text_question.html as the variable block_id.

Django how to set custom properties for a ModelForm

I have a forms.ModelForm 'CreateUserForm'.
I want to set a property for each form field to be later used in the template.
In this case, I want to set a icon name to specify which icon name should be used for each field.
class CreateUserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput)
icon_names = ['person', 'email', 'enhanced_encryption']
class Meta:
model = User
fields = ['username', 'email', 'password']
I've had trouble iterating over both the field AND the field's property 'icon_names'. I can't really zip() without losing functionality.
Currently I've hacked together iteration by using the 'forloop.parentloop.counter'
{% for field in form %}
<div class="form-group">
<div class="input-field">
<i class="icons">
{% for icon in form.icon_names %}
{% if forloop.parentloop.counter == forloop.counter %}
{{ icon }}
{% endif %}
{% endfor %}
</i>
<input type="text" id="autocomplete-input" class="autocomplete">
<label class="control-label" for="autocomplete-input">{{ field.label_tag }}</label>
</div>
</div>
{% endfor %}
Which produces the intended result, but it seems redundant, especially if I wanted to add another field property in the future.
What's the proper way to do this?
One idea would be to pass the zipped list in the context, such as:
context = {'fields_with_icons': zip(form.icon_names, [field for field in form])}
and then
{% for field, icon in fields %}
{{ field }}
{{ icon }}
{% endfor %}
There are two ways i could do this both involving adding an extra html attribute on the fields widget
see age field below, i would use the self.fields to get the field widget and add the extra icon attribute on its attrs dictionary...for this to work you should ensure it comes after the call to super().__init__(*args, **kwargs) others the self.fields will not have been populated....i'd use this when i dont have anything else i need to adjust on the widget class.
https://docs.djangoproject.com/en/2.0/ref/forms/widgets/#styling-widget-instances
see name field below, You could do this on the Meta class https://docs.djangoproject.com/en/2.0/topics/forms/modelforms/#overriding-the-default-fields
The form
class PersonForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['age'].widget.attrs['icon'] = 'age'
class Meta:
model = models.Person
fields = ('name', 'age')
widgets = {
'name': forms.TextInput(attrs={'icon': 'email'})
}
And on the template when looping over the fields id get it like this
{% for field in form %}
{{ field.field.widget.attrs.icon }}
{% endfor %}

how to change the template?

please help to solve the problem.
I made a form for uploading photos.
models:
class UserProfile(User):
drum_photo = models.ImageField(
'Фото инструмента',
upload_to='userprofile/drum_photo/',
blank=True,
null=True,
)
forms:
class DrumDataForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = (
'drum_photo',
)
template.html:
<div class="cell image_outer">
<label class="label">{{ form.drum_photo.label }}</label>
{{ form.drum_photo }}
{% if drum_photo %}
{% thumbnail drum_photo "230x230" crop="center" as im %}
<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
{% endthumbnail %}
{% endif %}
{{ form.drum_photo.errors }}
</div>
resulting browser displays the following markup:
<div class="cell image_outer">
<label class="label">foto</label>
now: userprofile/drum_photo/kYNP5kPnTOU_2.jpg <input id="drum_photo-clear_id" name="drum_photo-clear" type="checkbox"> <label for="drum_photo-clear_id">Clear</label><br>Изменить: <input id="id_drum_photo" name="drum_photo" type="file">
<img src="/media/cache/0a/ca/0acac93baa399abad7d3c048ff20d5db.jpg" width="230" height="230">
</div>
the problem is that I need to change the layout (add or remove some of the elements). please tell me where you store templates that need to change?
I think what you are asking is where is the template that renders a form field. This is controlled by the widget which is used to render the field.
A django form field contains two things:
Widget
Validation
The HTML and rendering is the responsibility of the widget. Django comes with a collection of widgets that are used in forms. Most of these correspond to the normal form elements, but there are some specialized ones.
To customize what HTML a form field will output, you need to customize the widget that is used to render the field.
You file upload form uses a FileField which uses by default the ClearableFileInput widget.
Each widget takes an optional attrs argument, which you can use to control the HTML attributes for the element that is rendered. For example, if I want to change the width and height:
class DrumDataForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(DrumDataForm, self).__init__(*args, **kwargs)
self.fields['drum_photo'] = forms.FileUploadField(
widget = forms.ClearableFileInput(
attrs={'width': 200,
'height': 300}))
class Meta:
model = UserProfile
If you want more styling flexibility on your form fields see the new form assets section, which allows you to customize the CSS that is used to style forms.
For even more customization you can create your own widget by subclassing the base widget classes; or by using a third party library like django-crispy-forms.

Django - ModelChoiceField empty choice missing

Django docs say when using ModelChoiceField on a form and form is set to required=False and model is set to blank=True in addition to no default values, then I should receive a free empty choice within my select tag. I am not. Any ideas to what I've done to lose the empty choice?
models.py
class Location(models.Model):
location_name = models.CharField(max_length=50, blank=True)
address = models.CharField(max_length=50)
active = models.BooleanField(default=True)
def __unicode__(self):
return self.location_name
forms.py
class CalcForm(forms.Form):
startAddr = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
waypoint1 = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
waypoint2 = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
...
endAddr = forms.ModelChoiceField(queryset=Location.objects.all(), required=False)
template.html
<form action="../calcview" method="get">{% csrf_token% }
<label>
<div>Start Address</div>
<select name="startAddr">
{% for location in form.fields.startAddr.queryset %}
<option value = "{ location.location_name }">{{location.location_name}}/option>
{% end for %}
</select>
...
</form>
You will only get the 'free' empty option if you allow django forms to render the form field automatically. Because you are rendering it yourself in your template (and I don't know why you would want to do that...) you would need to add the empty select yourself:
<select name="startAddr">
<option value="">-----------</option>
{% for location in form.fields.startAddr.queryset %}
<option value = "{ location.location_name }">{{location.location_name}}</option>
{% end for %}
</select>
You can test this by allowing django form to render it for you:
<div>Start Address</div>
{{ form.startAddr }}
You can use the empty_label on your ModelChoiceField.
startAddr = forms.ModelChoiceField(empty_label='---------', queryset=Location.objects.all(), required=False)
Then render the form field into the template.
{{ form.startAddr }}
https://docs.djangoproject.com/en/2.1/ref/forms/fields/#django.forms.ModelChoiceField.empty_label
I know, you asked more than half a year ago, but I thought I post another answer anyway. Because I think there is a more elegant solution that uses the "empty_label" that you can define in your form.
You can access this empty_label attribute through form.fields.startAddr.empty_label or form.startAddr.field.empty_label in your template. So you can include it like this:
<select id="{{ form.fields.startAddr.id_for_label }}" name="{{ form.fields.startAddr.html_name }}">
<option value="">{{ form.fields.startAddr.empty_label }}</option>
{% for location in form.fields.startAddr.queryset %}
<option value="{{ location.location_name }}">{{ location.location_name }}/option>
{% endfor %}
</select>
I wonder if you did not actually want to use {{ location.id }} as value? Or is the location_name unique? As it could even be empty, the auto-generated id might be better as a reference.
As you can see, I have also replaced name="startAddr" with information the form object provides anyway for every field: id="{{ form.fields.startAddr.id_for_label }}" name="{{ form.fields.startAddr.html_name }}. Using these variables should make your template code more flexible and robust.
For more details, please check the Django documentation:
ModelChoiceField: https://docs.djangoproject.com/en/dev/ref/forms/fields/#modelchoicefield
Automatic primary key field: https://docs.djangoproject.com/en/dev/topics/db/models/#automatic-primary-key-fields

Categories

Resources