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 %}
Related
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 have a form which has one checkboxselectmultiple field:
class CreateRecipeForm(forms.ModelForm):
class Meta:
model = Recipe
fields = ('name', 'image', 'description', 'cooking_time', 'tags')
widgets = {
...
'tags': forms.CheckboxSelectMultiple(),
...
}
I can iterate through field's options like:
{% for option in form.tags %}
{{ option.tag }} {{ option.choice_label }}
{% endfor %}
How would I render {{ option.tag }} as an input field? i.e:
<input type="checkbox"...>
Thanks.
In fact after reading some django docs on this topic I understood that that the fields of CheckboxSelectMultiple are already rendered as input fields. So if you don't need to add specific values to each field you can do it straight in form by adding attrs to widget:
widgets = {
...
'tags': forms.CheckboxSelectMultiple(attrs={'class': 'anyclass'}),
...
}
But if you need to add different parameters to each field you can do it in template this way:
{% for obj_choice, obj_value in form.obj.field.choices %}
<div class="tags__item">
<input type="checkbox" name="tags" value="{{obj_choice.instance.pk}}"
id="id_{{obj_choice.instance.value}}"
class="tags__checkbox tags__checkbox_style_{{obj_choice.instance.style}}">
<label for="id_{{obj_choice.instance.value}}" class="tags__label">
{{obj_choice.instance.template_name}}
</label>
</div>
{% endfor %}
I would like to populate my dropdown menu with records from the Subject table which is a many to many choices field that is populated with subjects by adding them manually from the admin page. A course can have many subjects such as "business" and "marketing".
Code:
https://dpaste.de/825n
How would I do that with django-select2 or use a form with model select or multiple model select?
https://docs.djangoproject.com/en/2.2/ref/forms/fields/#modelchoicefield
https://docs.djangoproject.com/en/2.2/ref/forms/fields/#modelmultiplechoicefield
https://django-select2.readthedocs.io/en/latest/
Or maybe I could do it with a for loop on the template?
For loops I have tried but no luck:
https://dpaste.de/5MVi
Desired Result:
https://imgur.com/a/Iw9lk6I
Can someone please help me figure it out? I have been stuck for a while now.
here hope this helps your missing the .all() on while querying the many to many fields. you're also not going deep enough to the actual name of the many to many fields so you're trying to print the object on your loop.
example view:
def tester(request):
tes = Test.objects.get(id=1)
testlist = tes.category.all()
context = {
'test': testlist,
}
return render(request, 'core/t.html', context)
example loop:
{% for item in test %}
<p>- {{item.cat}}</p>
{% endfor %}
example model:
class cats(models.Model):
cat = models.CharField(max_length=10,)
class Test(models.Model):
name = models.CharField(max_length=10,)
category = models.ManyToManyField(cats)
nested loop example:
{% for item in item_list %}
<h2>{{ item.name }}</h2>
<ul>
{% for sub in item.subjects.all %}
<li>{{ sub.name }}</li>
{% endfor %}
</ul>
{% endfor %}
After creating your model form you can use something like this to get a dropdown
class CourseForm(forms.ModelForm):
subjects = forms.ModelMultipleChoiceField(
queryset=Subject.objects.all(),
required=True,
)
class Meta:
model = Course
fields = [......, subjects]
or you can use the other widget, widget=forms.CheckboxSelectMultiple,depending on your requirement
<form method="post" action="">
<div>
{% csrf_token %}
{{ form }}
<input type="submit" class="btn btn-primary" id="submit" value="Save">
</div>
</form>
Add a create view to create a course something like below
class CourseCreateView(CreateView):
model = Course
form_class = CourseForm
template_name = 'course_form.html'
success_url = reverse_lazy('/')
I have a model choice field(Many to many relation), When I try to submit the form it throws an error Select a valid choice. That choice is not one of the available choices.
I have also added in forms __init__ but still facing same error. I think the error is due to string, integer in checkbox value. Can anyone help me to fix this one. Posting my code here
class CustomSignupForm(forms.ModelForm):
telephone = forms.CharField(label=_('Telephone'), required=False)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(CustomSignupForm, self).__init__(*args, **kwargs)
self.fields['categories'] = forms.ModelChoiceField(queryset=Category.objects.filter(status=True), widget=forms.CheckboxSelectMultiple)
for name, field in self.fields.iteritems():
attr = {'class': 'custom-class'}
if field.label:
attr['placeholder'] = field.label
field.widget.attrs.update(attr)
Categories is the field with many to many selection, which is not allowing me to submit the form.
Model
class Category(DateUpdate):
title = models.CharField(max_length=256,null=True,blank=False)
status = models.BooleanField(default=True)
def __unicode__(self):
return self.title
Form
<div class="form-group">
<label>
{{ form.categories.label }}
</label>
{% for check_choice in form.categories %}
{{ check_choice.tag }}
{{check_choice.choice_label}}
{% endfor %}
{% if form.categories.errors %}
<span class="error">
{{ form.categories.errors.as_text }}
</span>
{% endif %}
</div>
I want to show a title and description from a db query in each form, but I don't want it to be in a charfield, I want it to be html-formatted text.
sample template code:
{% for form, data in zipped_data %}
<div class="row">
<div class="first_col">
<span class="title">{{ data.0 }}</span>
<div class="desc">
{{ data.1|default:"None" }}
</div>
</div>
{% for field in form %}
<div class="fieldWrapper" style="float: left; ">
{{ field.errors }}
{{ field }}
</div>
{% endfor %}
{% endfor %}
Is this the most idiomatic way of doing this? Or, is there a way to add text that will not be displayed inside of a textarea or text input to my model:
class ReportForm(forms.Form):
comment = forms.CharField()
?
Instead of zipping your forms with the additional data, you can override the constructor on your form and hold your title/description as instance-level member variables. This is a bit more object-oriented and learning how to do this will help you solve other problems down the road such as dynamic choice fields.
class MyForm (forms.Form):
def __init__ (self, title, desc, *args, **kwargs):
self.title = title
self.desc = desc
super (MyForm, self).__init__ (*args, **kwargs) # call base class
Then in your view code:
form = MyForm ('Title A', 'Description A')
Adjust accordingly if you need these values to come from the database. Then in your template, you access the instance variables just like you do anything else, e.g.:
<h1>{{ form.title }}</h1>
<p>{{ form.desc }}</p>
From the way you phrased your question, I think you probably have some confusion around the way Django uses Python class attributes to provide a declarative form API versus instance-level attributes that you apply to individual instances of a class, in this case your form objects.
Check out this link for a good discussion on the distinction
And this one
I just created a read-only widget by subclassing the text input field one:
class ReadOnlyText(forms.TextInput):
input_type = 'text'
def render(self, name, value, attrs=None):
if value is None:
value = ''
return value
And:
class ReportForm(forms.Form):
comment = forms.CharField(widget=ReadOnlyText, label='comment')
I had to solve a similar problem and like your idea Andrei. I had some issues using it though, as, if there were validation errors, the value of the read-only field would get lost. To solve this, I did something similar but overrode HiddenInput instead and kept the value in a hidden form field. ie:
class ReadOnlyText(forms.HiddenInput):
input_type = 'hidden'
def render(self, name, value, attrs=None):
if value is None:
value = ''
return mark_safe(value + super(ReadOnlyTextWidget, self).render(name, value, attrs))
class ReportForm(forms.Form):
comment = forms.CharField(widget=ReadOnlyText, label='comment')
I think you can get it with "{{ field.value }}". Maybe it's the easier way.
{% for form in formset %}
{% for field in form %}
{% if forloop.counter = 1 %}
<td><img src="{{ MEDIA_URL }}{{ field.value }}"/></td>
{% endif %}
{% if forloop.counter = 2 %}
<td>{{ field.value }}</td>
{% endif %}
{% if forloop.counter > 2 %}
<td>{{ field }}{{ field.errors }}</td>
{% endif %}
{% endfor %}
{% endfor %}