I want to re-use a template I have with my WTForms form:
<th>${form.name.label}</th>
<td>${form.name()}</td>
...
However, on my edit page, I want the input fields to display as normal (TextField, SelectField, etc.), while on my view page, I want to just display the value of the property, not the input field with the value.
Edit page:
<th>Name:</th>
<td><input type="text" value="Current Name" name="name" id="name"/></td>
View page:
<th>Name:</th>
<td>Current Name</td>
I know I can access a field's value via form.name.data, but is there any way I can keep the same template with form.name() being called and somehow toggle whether that outputs <input type="text"... or Current Name?
I created a custom widget:
from wtforms.fields import Field
class PlainTextWidget(object):
def __call__(self, field, **kwargs):
return field.data if field.data else ''
Then, for my view page, I added the following:
form = MyForm(obj=myDataRow)
fields = [val for val in form._fields]
for fieldName in fields:
fieldProp = getattr(form, fieldName)
setattr(fieldProp, 'widget', PlainTextWidget())
Sarah's answer above led me to the solution to a related problem: What if you want some of your fields to be read only? In that case, instead of doing run-time surgery on the form object, you could define a new ROTextField variant (for example), that always renders to the pure value. For example:
from wtforms.widgets import Input
from wtforms.fields import StringField
class TextOutput(Input):
def __call__(self, field, **kwargs):
return kwargs.get('value', field._value())
class ROTextField(StringField):
widget = TextOutput()
Now define your field with ReadOnly attributes:
class UserPrefs(Form):
name = ROTextField('name', default='Jon')
# ...
Thinking about this problem helped me better understand how WTForms work. Leaving this here in case this might help someone else work through related issues.
Based on Sarah's answer and code found in WTForms-Components I use the following to quickly turn all a form's fields into read-only and disabled fields.
Suppose we have a ProfileForm defined as follows:
class ProfileEditForm(Form):
title = StringField("Title", validators=[validators.required("Please enter your title.")])
first_name = StringField("First Name", validators=[validators.required("Please enter your first name.")])
middle_name = StringField("Middle Name")
last_name = StringField("Last Name", validators=[validators.required("Please enter your last name.")])
organisation = StringField("Company Name", validators=[validators.required("Please enter your company name.")])
organisation_website = StringField("Company Website")
# more fields ...
Define the following class (based on ReadOnlyWidgetProxy from WTForms-Components):
class ReadOnlyAndDisabledWidgetProxy(object):
def __init__(self, widget):
self.widget = widget
def __getattr__(self, name):
return getattr(self.widget, name)
def __call__(self, field, **kwargs):
kwargs.setdefault('readonly', True)
kwargs.setdefault('disabled', True)
return self.widget(field, **kwargs)
Now inherit from ProfileForm as follows:
class ReadOnlyProfileForm(ProfileForm):
def __init__(self, *args, **kwargs):
super(ReadOnlyProfileForm, self).__init__(*args, **kwargs)
for field_name in self._fields:
field_property = getattr(self, field_name)
field_property.widget = ReadOnlyAndDisabledWidgetProxy(field_property.widget)
Related
i'm taking values from database table in views file and has to render those values to a form in template file which is created by using the forms class and i have to show those values for some fields and make them immutable.
class OrderForm(forms.Form):
pid=forms.IntegerField()
pname=forms.CharField()
pprice=forms.FloatField()
person_name=forms.CharField(max_length=40)
emailid=forms.EmailField()
address=forms.CharField(max_length=40)
city=forms.CharField(max_length=20)
state=forms.CharField(max_length=20)
zip=forms.IntegerField()
card=forms.IntegerField()
exp= forms.DateField()
cvv=forms.IntegerField()
def order(request,pk):
pid=pk
user_name=request.user.username
qs=Product.objects.get(pid=pid)
pname=qs.pname.format()
list={'username':user_name,'pid':pid,'pname':pname}
form=OrderForm
return render(request,'order.html',{'data':list,'form':form})
i expect input filed with value that i passed by default which is immutable and when i submit i have to get same value i passed
Looks to me like you're better off using a ModelForm. It would be something like:
class OrderForm(forms.ModelForm)
class Meta:
model = Order
widgets = {
`immutable_field` : forms.TextInput(attrs={'readonly':True})
}
def order(request,pk):
pid=pk
user_name=request.user.username
qs=Product.objects.get(pid=pid)
pname=qs.pname.format()
list={'username':user_name,'pid':pid,'pname':pname}
form=OrderForm()
form.fields['immutable_field'] = "Some Value"
return render(request,'order.html',{'data':list,'form':form})
If you already have an order then you can prepopulate the fields with form=OrderForm(instance=order)
Make field as disable from Form init method and pass initial value from view section
class OrderForm(forms.Form):
pid=forms.IntegerField()
pname=forms.CharField()
pprice=forms.FloatField()
person_name=forms.CharField(max_length=40)
emailid=forms.EmailField()
address=forms.CharField(max_length=40)
city=forms.CharField(max_length=20)
state=forms.CharField(max_length=20)
zip=forms.IntegerField()
card=forms.IntegerField()
exp= forms.DateField()
cvv=forms.IntegerField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['username'].disabled = True
self.fields['pid'].disabled = True
self.fields['pname'].disabled = True
Here in view you can pass dictionary to form as initial value of fields.
def order(request,pk):
pid=pk
user_name=request.user.username
qs=Product.objects.get(pid=pid)
pname=qs.pname.format()
initial={'username':user_name,'pid':pid,'pname':pname}
form=OrderForm(initial=initial)
return render(request,'order.html',{'data':initla,'form':form})
I have a form that I need to show my project outside the area of administration, some fields can not be edited but can see them.
To do this would be great "AdminReadonlyField" found in "django.contrib.admin.helpers" The problem is that you can not do.
I have tried to create some widget that can replace this complex class, but I can not get it to work properly with DateTiemField fields.
class UserUpdateForm(forms.ModelForm):
"""
We need field "date_joined" can not be edited but can see them
"""
class Meta:
model = User
fields = ('first_name', 'last_name',
'email', 'date_joined', 'slug')
def __init__(self, user, *args, **kwargs):
kwargs['instance'] = user
super(UserUpdateForm, self).__init__(*args, **kwargs)
self.fields['date_joined'].widget = widgets.CMDateTimeText()
def clean_date_joined(self):
instance = getattr(self, 'instance', None)
if instance and instance.pk:
return instance.date_joined
else:
return self.cleaned_data['date_joined']
My code, something is not right.
class CMDateTimeText(DateTimeBaseInput):
"""
A SplitDateTime Widget that has some admin-specific styling.
Hereda Field and Widget.
"""
format_key = 'DATETIME_FORMAT'
def __init__(self, attrs=None, format=None):
# Use slightly better defaults than HTML's 20x2 box
default_attrs = {'class': 'date_id'}
if attrs:
default_attrs.update(attrs)
super(CMDateTimeText, self).__init__(attrs=default_attrs, format=format)
def render(self, name, value, attrs=None):
if value is None:
value = ''
value = self._format_value(value)
final_attrs = self.build_attrs(attrs, name=name)
return format_html('<p{}>{}</p>', flatatt(final_attrs), conditional_escape(value))
Result image:
any idea how to do "AdminReadonlyField"" any view or form?
So after hours of looking for various solutions, I found out how to do it the Django way.
Simply add the attribute disabled to the field in the form (not the widget!):
# in __init__() with crispy-forms for instance
self.fields['field'].disabled = True
# as form field
field = forms.CharField(disabled=True)
And it works... Django is taking care of not saving the field, if some hacker tampered with it although it's disabled. Only works with Django 1.9+.
I have a foreign key field in my form, initialized with:
form=myform(intial= {'val':abc.objects.get(pk=id)})
I have tried the following set of codes individually to make the field "val" as a read only value.
form.fields['val'].widget = forms.HiddenInput()
form.fields['val'].widget.attrs['readonly'] = True
form.fields['val'].widget.attrs['disabled'] = True
form.fields['val'].widget.attrs['disabled'] = 'disabled'
form.fields['val'].widget.attrs['disabled'] = False
form.fields['val'].widget.attrs['display_only'] = True
form.fields['val'].widget.attrs['editable'] = False
Only HiddenInput worked but it didn't show the field in the form, while disabled showed an error. readonly worked on all fields except the foreign key field.
I did this by overriding the Select widgets render method, and changing its output.
class ReadOnlySelect(Select):
"""
This should replace the Select widget with a disabled text widget displaying the value,
and hidden field with the actual id
"""
def render(self, name, value, attrs=None, choices=()):
final_attrs = self.build_attrs(attrs, name=name)
display = "None"
for option_value, option_label in chain(self.choices, choices):
if str(option_value) == (value) :
display = option_label
output = format_html('<input type=text value="%s" disabled="disabled" ><input type="hidden" value="%s" %s> ' % (display, value, flatatt(final_attrs)))
return mark_safe(output)
fuller example on Django snippets.
https://djangosnippets.org/snippets/10436/
I have similar problem and I decided to use different approach. Firstly I define my own class for form, for example this way:
class MyForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
class Meta:
model = YourModel
exclude = ('foreign_key_field')
And then in models myModel did sth like this:
foreign_key_field = models.ForeignKey(KeyField)
def set_values(self, *args, **kwargs):
foreign_key_field = kwargs.pop('foreign_key_field')
I define function to set fields values that are read only. I call this method
after creating form object in views.py. (after submiting form)
And at last views.py:
if form.is_valid():
new_instance = form.save(commit=False)
new_instance.set_values(
foreign_key_field='YOUR VALUE',
)
new_instance.save()
I exclude this fields from form but after creating instance I am setting values. After setting values I save to db.
My form validation is working nearly complete, I just have 2 cases I don't know exactly how to solve: 1) The password field should be required of course but I also provide the possibility to log in with google or facebook account via OAuth and then name gets prefilled but I remove the password field completely from the form is there is a user (google) or a facebook user object:
<tr><td>
<br /> {% if user or current_user %} {% else %}
<div class="labelform">
{% filter capitalize %}{% trans %}password{% endtrans %}{% endfilter %}:
</div>
</td><td> <div class="adinput">{{ form.password|safe }}{% trans %}Choose a password{% endtrans %}</div>{% endif %}
</td></tr>
So for these users who already are logged in and the password field has no meaning, I need some logic to make that field conditionally optional. I was thinking that I could have a variable for logged_in + a method in my form class such as this:
class AdForm(Form):
logged_in = False
my_choices = [('1', _('VEHICLES')), ('2', _('Cars')), ('3', _('Bicycles'))]
name = TextField(_('Name'), [validators.Required(message=_('Name is required'))], widget=MyTextInput())
title = TextField(_('title'), [validators.Required(message=_('Subject is required'))], widget=MyTextInput())
text = TextAreaField(_('Text'),[validators.Required(message=_('Text is required'))], widget=MyTextArea())
phonenumber = TextField(_('Phone number'))
phoneview = BooleanField(_('Display phone number on site'))
price = TextField(_('Price'),[validators.Regexp('\d', message=_('This is not an integer number, please see the example and try again')),validators.Optional()] )
password = PasswordField(_('Password'),[validators.Optional()], widget=PasswordInput())
email = TextField(_('Email'), [validators.Required(message=_('Email is required')), validators.Email(message=_('Your email is invalid'))], widget=MyTextInput())
category = SelectField(choices = my_choices, default = '1')
def validate_name(form, field):
if len(field.data) > 50:
raise ValidationError(_('Name must be less than 50 characters'))
def validate_email(form, field):
if len(field.data) > 60:
raise ValidationError(_('Email must be less than 60 characters'))
def validate_price(form, field):
if len(field.data) > 8:
raise ValidationError(_('Price must be less than 9 integers'))
def validate_password(form, field):
if not logged_in and not field:
raise ValidationError(_('Password is required'))
Will the above validate_password work to achieve the desired effect? Is there another better way? Another way I could think is to have 2 different form class and in http post I instanciate the form class it should be:
def post(self):
if not current_user:
form = AdForm(self.request.params)
if current_user:
form = AdUserForm(self.request.params)
I also need conditional validation for the category field, when a certain category is selected then more choices appear and these should have validation only for a certain base-category eg. user selects "Car" and then via Ajax can choose registration data and mileage for the car and these fields are required given that the category Car was selected.
So it might be two questions but both cases relate to how I can make a field "conditionally optional" or "conditionally required".
My form looks like this
And for a logged in user I prefill the name and email address and the pasword field is simply not used, so the password field neither fits being "optional" nor "required", it would need something like "conditionally optional" or "conditionally required."
Thanks for any answer or comment
I'm not sure this quite fits your needs, but I've used a RequiredIf custom validator on fields before, which makes a field required if another field has a value in the form... for instance, in a datetime-and-timezone scenario, I can make the timezone field required to have a value if the user has entered a datetime.
Updated to use "InputRequired" instead of "Required"
class RequiredIf(InputRequired):
# a validator which makes a field required if
# another field is set and has a truthy value
def __init__(self, other_field_name, *args, **kwargs):
self.other_field_name = other_field_name
super(RequiredIf, self).__init__(*args, **kwargs)
def __call__(self, form, field):
other_field = form._fields.get(self.other_field_name)
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if bool(other_field.data):
super(RequiredIf, self).__call__(form, field)
The constructor takes the name of the other field that triggers making this field required, like:
class DateTimeForm(Form):
datetime = TextField()
timezone = SelectField(choices=..., validators=[RequiredIf('datetime')])
This could be a good starting point for implementing the sort of logic you need.
I found this question helpful and based on the answer of #dcrosta I created another validator which is optional. The benefit is that you can combine it with other wtforms validators. Here is my optional validator which checks another field. Because I needed to check the value of the other field against some certain value I added a custom check for value:
class OptionalIfFieldEqualTo(wtf.validators.Optional):
# a validator which makes a field optional if
# another field has a desired value
def __init__(self, other_field_name, value, *args, **kwargs):
self.other_field_name = other_field_name
self.value = value
super(OptionalIfFieldEqualTo, self).__init__(*args, **kwargs)
def __call__(self, form, field):
other_field = form._fields.get(self.other_field_name)
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if other_field.data == self.value:
super(OptionalIfFieldEqualTo, self).__call__(form, field)
The answer from #dcrosta is great, but I think some things have changed in wtforms since this answer. Inheriting from DataRequired adds a required attribute to the form field, so the conditional validator never gets called. I made a minor change to the class from #dcrosta that works with wtforms 2.1. This only over-rides field_flags so that browser validation is not done.
from wtforms.validators import DataRequired
class RequiredIf(DataRequired):
"""Validator which makes a field required if another field is set and has a truthy value.
Sources:
- http://wtforms.simplecodes.com/docs/1.0.1/validators.html
- http://stackoverflow.com/questions/8463209/how-to-make-a-field-conditionally-optional-in-wtforms
"""
field_flags = ('requiredif',)
def __init__(self, other_field_name, message=None, *args, **kwargs):
self.other_field_name = other_field_name
self.message = message
def __call__(self, form, field):
other_field = form[self.other_field_name]
if other_field is None:
raise Exception('no field named "%s" in form' % self.other_field_name)
if bool(other_field.data):
super(RequiredIf, self).__call__(form, field)
A more ideal solution would manage to do the validation in the browser, like the current behavior of DataRequired.
I have a form that inherits from 2 other forms. In my form, I want to change the label of a field that was defined in one of the parent forms. Does anyone know how this can be done?
I'm trying to do it in my __init__, but it throws an error saying that "'RegistrationFormTOS' object has no attribute 'email'". Does anyone know how I can do this?
Thanks.
Here is my form code:
from django import forms
from django.utils.translation import ugettext_lazy as _
from registration.forms import RegistrationFormUniqueEmail
from registration.forms import RegistrationFormTermsOfService
attrs_dict = { 'class': 'required' }
class RegistrationFormTOS(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):
"""
Subclass of ``RegistrationForm`` which adds a required checkbox
for agreeing to a site's Terms of Service.
"""
email2 = forms.EmailField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=75)), label=_(u'verify email address'))
def __init__(self, *args, **kwargs):
self.email.label = "New Email Label"
super(RegistrationFormTOS, self).__init__(*args, **kwargs)
def clean_email2(self):
"""
Verifiy that the values entered into the two email fields
match.
"""
if 'email' in self.cleaned_data and 'email2' in self.cleaned_data:
if self.cleaned_data['email'] != self.cleaned_data['email2']:
raise forms.ValidationError(_(u'You must type the same email each time'))
return self.cleaned_data
You should use:
def __init__(self, *args, **kwargs):
super(RegistrationFormTOS, self).__init__(*args, **kwargs)
self.fields['email'].label = "New Email Label"
Note first you should use the super call.
Here's an example taken from Overriding the default fields:
from django.utils.translation import ugettext_lazy as _
class AuthorForm(ModelForm):
class Meta:
model = Author
fields = ('name', 'title', 'birth_date')
labels = {
'name': _('Writer'),
}
help_texts = {
'name': _('Some useful help text.'),
}
error_messages = {
'name': {
'max_length': _("This writer's name is too long."),
},
}
You can set label as an attribute of field when you define form.
class GiftCardForm(forms.ModelForm):
card_name = forms.CharField(max_length=100, label="Cardholder Name")
card_number = forms.CharField(max_length=50, label="Card Number")
card_code = forms.CharField(max_length=20, label="Security Code")
card_expirate_time = forms.CharField(max_length=100, label="Expiration (MM/YYYY)")
class Meta:
model = models.GiftCard
exclude = ('price', )
You access fields in a form via the 'fields' dict:
self.fields['email'].label = "New Email Label"
That's so that you don't have to worry about form fields having name clashes with the form class methods. (Otherwise you couldn't have a field named 'clean' or 'is_valid') Defining the fields directly in the class body is mostly just a convenience.
Try on Models.py
email = models.EmailField(verbose_name="E-Mail Address")
email_confirmation = models.EmailField(verbose_name="Please repeat")
It don't work for model inheritance, but you can set the label directly in the model
email = models.EmailField("E-Mail Address")
email_confirmation = models.EmailField("Please repeat")
While using a class' constructor to change the field label for an instance works, it's better from an object-oriented design standpoint to make the change at the class level if the label doesn't need to be customized per-instance.
For example, the following will change the label on PasswordChangeForm's new_password2 field from "New password confirmation" to "Confirm Password":
class MyPasswordChangeForm(PasswordChangeForm):
PasswordChangeForm.base_fields['new_password2'].label = 'Confirm Password'
Take a look in django/forms/forms.py, you'll see that DeclarativeFieldsMetaclass removes the fields from a class' attribute list and places them in base_fields and declared_fields.
class Form(BaseForm, metaclass=DeclarativeFieldsMetaclass):
...
class DeclarativeFieldsMetaclass(MediaDefiningClass):
"""Collect Fields declared on the base classes."""
def __new__(mcs, name, bases, attrs):
# Collect fields from current class and remove them from attrs.
attrs['declared_fields'] = {
key: attrs.pop(key) for key, value in list(attrs.items())
if isinstance(value, Field)
}
new_class = super().__new__(mcs, name, bases, attrs)
# Walk through the MRO.
declared_fields = {}
for base in reversed(new_class.__mro__):
# Collect fields from base class.
if hasattr(base, 'declared_fields'):
declared_fields.update(base.declared_fields)
# Field shadowing.
for attr, value in base.__dict__.items():
if value is None and attr in declared_fields:
declared_fields.pop(attr)
new_class.base_fields = declared_fields
new_class.declared_fields = declared_fields
There's a comment in BaseForm that provides further explanation:
class BaseForm:
...
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
if all other solutions don't work, this one worked for me (to change the user class label to django contrib auth)
#in models.py
User._meta.get_field('username').verbose_name = "new name"