Add CSS to form field in django template - python

I want to use bootstrap on django form. To do so I need to add .form-control class to each field. My workaround is:
def __init__(self, *args, **kwargs):
super(SomeForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].widget.attrs = {
'class': 'form-control'
}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field }}
</div>
{% endfor %}
But I believe it's wrong to put CSS in python code (if I would want to use another CSS framework, I would have to change form class). How can I move the class to template? I would not like to use crispy not to be bound to bootstrap.

You can use a custom template tag for adding a class, see more information here
You will have to create a new module inside the templatetags directory:
myapp/
__init__.py
models.py
templatetags/
__init__.py
extra_filters.py <-- Name it as you prefer
views.py
forms.py
The content of your extra_filters.py module can be something like this:
from django import template
register = template.Library()
#register.filter(name='addclass')
def addclass(field, myclass):
return field.as_widget(attrs={"class": myclass})
Now in your template, load the new filter and use it as follows:
{% load extra_filters %}
{% for field in form %}
<div class="form-group">
{{ field.label_tag }}
{{ field|addclass:"form-control" }}
</div>
{% endfor %}

Add form-control css class to the widgets.
The form class is the access point of the input tags through widgets. By changing the widget attr from the constructor method in form.Form
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.visible_fields():
field.field.widget.attrs.update({'class': 'form-control'})

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".

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 %}

FormSet rendering always two forms as default Django

I have the following code for my FormSet:
Forms.py
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
CityFormSet = formset_factory(CityNameForm, min_num=1, max_num=5, validate_min=True, validate_max=True, formset=RequiredFormSet)
Template.html
{{ city_formset.management_form }}
{% for city_form in city_formset %}
{{ city_form.id }}
<div>
{{ city_form.city_name }}
</div>
<div>
{{ city_form.region }}
</div>
{% endfor %}
Creating and deleting forms in the formset is handled by JavaScript, but it is only activated when a button is clicked. If the user edits the JavaScript and removes the condition where it verifies how many forms the page must have, he can submit the page without any form in the formset.
If I set min_num=0 in the constructor, the user is able to use the scheme above and go over the validation. If I set it to min_num=1, he can't but two forms are generated within the formset.
Sure, just change this line:
CityFormSet = formset_factory(CityNameForm, min_num=1, max_num=5, validate_min=True, validate_max=True, formset=RequiredFormSet)
to this:
CityFormSet = formset_factory(CityNameForm, min_num=1, max_num=5,
validate_min=True, validate_max=True,
formset=RequiredFormSet, extra=0)
Also made your line a bit shorter so you don't have to scroll so much.
in formset_factory has option with extra, you can utilize as extra=2

Are You Human And Django HTML

I am trying to get AreYouAHuman capcha on my website,
I mostly got it working the problem is nothing shows up.
this is part of the render'd html
<label for="id_session_secret">Are You Human</label>
<br>
<div id="AYAH"></div><script type="text/javascript" src="https://ws.areyouahuman.com/ws/script/87e31f12336132203bdde2da8c93269fccdd5f52"></script>
<p></p>
and this is what i see in the browser
> Are You Human <div id="AYAH"></div><script type="text/javascript"
> src="https://ws.areyouahuman.com/ws/script/87e31f12336132203bdde2da8c93269fccdd5f52"></script>
This is my Implementation
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# turtle.forms.py
#
# Copyright 2013 Brian Scott Carpenter <talisman#talisman-Pangolin-Performance>
from django import forms
from django.conf import settings
import ayah
ayah.configure(
settings.AREYOUHUMAN_PUBLISHER_KEY,
settings.AREYOUHUMAN_SCORING_KEY,
)
from django.forms import Field, ValidationError, Form
from django.forms.widgets import Widget
#Create a wdiget that renders the AreYouHuman html
class AreYouHumanWidget(Widget):
def render(self, name, value, attrs=None):
return ayah.get_publisher_html()
#AreYouHuman uses a test field (session_secret) for validation
class AreYouHumanField(Field):
#Use the AreYouHuman HTML
widget = AreYouHumanWidget
#Validate agianst AreYouHuman
def validate(self, value):
if not ayah.score_result(value):
raise ValidationError("You may not be human.")
class Turtle_Form(forms.ModelForm):
''' This is code to make sure the User automatically saves the
user to the database in context.
'''
session_secret = AreYouHumanField(label="Are You Human")
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(Turtle_Form, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj #<--- Return saved object to caller.
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(Turtle_Form, self).__init__(*args, **kwargs)
the template looks like
{% extends "base.html" %}
{% load i18n zinnia_tags %}
{% load url from future %}
{% block title %}tempilo.org{% endblock %}
{% block content %}
<form action="" method="post">{% csrf_token %}
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }}
<br>
{{ field | safe }}
<p></p>
</div>
{% endfor %}
<p><input type="submit" value="Save" /></p>
</form>
{% endblock %}
You might need to wrap the call to ayah.get_publisher_html() in the widget render method with a call to django.utils.safestring.mark_safe() to prevent it being autoescaped.

Form.media not being injected into a template

I wrote a simple app that is a custom DateField widget for mezzanine. Afaik it's, it's a simple case and besides overextending a template it would be the same in pure django or django-cms (feel free to correct me if I'm wrong).
The widget:
class DatePickerInput(forms.DateInput):
def __init__(self, attrs = None, format = None):
super(DatePickerInput, self).__init__(attrs, format)
self.attrs["class"] = "datepicker"
class Media:
css = { "all": ('css/ui-lightness/jquery-ui-1.10.3.custom.min.css',) }
js = (
"mezzanine/js/" + getattr(settings, "JQUERY_UI_FILENAME", "jquery-ui-1.9.1.custom.min.js"),
"js/datepicker_setup.js",)
I overextend base.html template to insert form.media:
{% overextends "base.html" %}
{% load pages_tags mezzanine_tags i18n future staticfiles %}
{% block extra_head %}{{ block.super }}
{{ form.media }}
{% endblock %}
Now, I create a form for my model class.
Here's the class:
class PlayerProfile(models.Model):
user = models.OneToOneField("auth.User")
# Can be later changed to use a setting variable instead of a fixed date
date_of_birth = models.DateField(default=date(1990, 1, 1))
Here's the model form:
from DateWidgets.widgets import DatePickerInput
from PlayerProfiles.models import PlayerProfile
class EditPlayerProfileForm(Html5Mixin, forms.ModelForm):
class Meta:
model = PlayerProfile
fields = ("date_of_birth", )
widgets = { 'date_of_birth': DatePickerInput }
def __init__(self, *args, **kwargs):
super(EditPlayerProfileForm, self).__init__(*args, **kwargs)
Here's the view:
#login_required
def profile_update(request, template="accounts/account_profile_update.html"):
"""
Profile update form.
"""
pform = forms.EditPlayerProfileForm
player_form = pform(request.POST or None, request.FILES or None, instance=request.user.playerprofile)
context = {"pform": player_form, "title": _("Update Profile"), "profile_user": request.user}
return render(request, template, context)
Here's the template:
{% overextends "accounts/account_profile_update.html" %}
{% load i18n mezzanine_tags %}
{% block main %}
<fieldset>
<legend>{{ title }}</legend>
<form method="post"{% if pform.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% fields_for pform %}
<div class="form-actions">
<input class="btn btn-primary btn-large" type="submit" value="{{ title }}">
</div>
</form>
</fieldset>
{% endblock %}
Now if I view the form in a browser, the custom widget is there (I can tell because the input html tag has my custom class attribute value) but it doesn't inject the form media, it's missing. Any idea what's going on here? Thanks in advance! Cheers :-)
form isn't available in the template, because you've called your form variable pform.
Try {{ pform.media }}.
I was googled here with the same problem under different conditions and spotted another error that may appear under other conditions (my case is extending change_form.html in Django 2.1): I had to use extrahead instead of extra_head
{% block extrahead %}{{ block.super }}
{{ form.media }}
{% endblock %}

Categories

Resources