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"
Related
I have a customuser model with
class customuser(AbstractUser):
# additional fields
def __str__(self):
return self.username
I have another model, that becomes the foreign key for this model
class bfs_support_ticket_model(models.Model):
ticket_created_by = models.ForeignKey(customuser, on_delete = models.CASCADE)
Why doesn't Django renders username for the form, but render or returns username everywhere correctly
class ticket_edit_form(ticket_create_form):
# ticket_created_by = forms.CharField(widget=forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True})) # does not work in this way
def __init__(self, *args, **kwargs):
super(ticket_edit_form,self).__init__(*args, **kwargs)
# self.fields['ticket_created_by'].disabled = True
self.fields['ticket_created_by'].widget = forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True}) # doesnot work in this way too
class Meta:
model=bfs_support_ticket_model
exclude=['ticket_last_updated_by']
When the form is rendered it just prints the customuser.id instead of customuser.username
But when no form initialization is made, it return the customuser.username correctly
i.e. when
class ticket_edit_form(ticket_create_form):
def __init__(self, *args, **kwargs):
super(ticket_edit_form,self).__init__(*args, **kwargs)
self.fields['ticket_created_by'].disabled = True # only this line present it renders customuser.username
class Meta:
model=bfs_support_ticket_model
exclude=['ticket_last_updated_by']
Please help me, where I am going wrong
Edit:
Why does
self.fields['ticket_created_by'].disabled = True # prints username
while
self.fields['ticket_created_by'].widget = forms.TextInput(attrs={'class' : 'form-control', 'readonly' : True}) # this doesn't
class bfs_support_ticket_model(models.Model):
ticket_created_by = models.ForeignKey(customuser, on_delete = models.CASCADE)
def __str__(self):
return self.ticket_created_by.customuser.username
Observations:
Since the field is a Foreignkey, it gets rendered as a modelchoicefield, hence initializing the field as charfield, only prints the exact value present in the db (in this case customuser.id)
To make it render properly, I had to initialize it as a modelchoicefield, like
ticket_created_by = forms.ModelChoiceField(queryset = customuser.objects.all(), widget = forms.Select(attrs={'class' : 'custom-select custom-select-sm mb-3', 'disabled' : True}))
Please advice, if this is a correct solution
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+.
models.py:
class Tag(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(User)
tag = models.ManyToManyField(Tag)
title = models.CharField(max_length=100)
content = models.TextField()
created = models.DateTimeField(default=datetime.datetime.now)
modified = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return '%s,%s' % (self.title,self.content)
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
class PostModelFormNormalUser(forms.ModelForm):
class Meta:
model = Post
widgets = { 'tag' : TextInput() }
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
views.py:
if request.method == 'POST':
form = PostModelFormNormalUser(request.POST)
print form
print form.errors
tagstring = form.data['tag']
splitedtag = tagstring.split()
if form.is_valid():
temp = form.save(commit=False)
temp.user_id = user.id
temp.save()
l = len(splitedtag)
for i in range(l):
obj = Tag(name=splitedtag[i])
obj.save()
post.tag_set.add(obj)
post = Post.objects.get(id=temp.id)
return HttpResponseRedirect('/viewpost/' + str(post.id))
else:
form = PostModelFormNormalUser()
context = {'form':form}
return render_to_response('addpost.html', context, context_instance=RequestContext(request))
Here form.is_valid() is always false because it gets the tag as string from form. But it expects list as form.data['tag'] input. Can anyone tell me how can i fix it?
How can i write a custom widget to solve this?
I don't think you need a custom widget (you still want a TextInput), you want a custom Field. To do this, you should subclass django.forms.Field. Unfortunately the documentation is scant on this topic:
If the built-in Field classes don’t meet your needs, you can easily create custom Field classes. To do this, just create a subclass of django.forms.Field. Its only requirements are that it implement a clean() method and that its init() method accept the core arguments mentioned above (required, label, initial, widget, help_text).
I found this blog post that covers both custom widgets and fields in more depth. The author disagrees with the documentation I quoted above - it's worth reading over.
For your specific situation, you would do something like this (untested):
class MyTagField(forms.Field):
default_error_messages = {
'some_error': _(u'This is a message re: the somr_error!'),
}
def to_python(self, value):
# put code here to coerce 'value' (raw data from your TextInput)
# into the form your code will want (a list of Tag objects, perhaps)
def validate(self, value):
if <not valid for some reason>:
raise ValidationError(self.error_messages['some_error'])
Then in your ModelForm:
class PostModelFormNormalUser(forms.ModelForm):
tag = MyTagField()
class Meta:
model = Post
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
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)
I have a model that contains a foreign key value, then in the form generated from this model as a ModelChoiceField. I want to auto select the user's (update_author).
I've tried the below code, using the initial property.
The view creates a formset with the the dates initialized to now() for the empty form. But, I want also the update_author field to be initialized to some value, and it does not work.
Even if the dev shell returns the correct user (print), this one is still not selected in the list.
How to initialize a django.forms.ModelChoiceField, binded with foreign key, with a value?
models.py
from django.db import models
import datetime
class Author(models.Model):
name = models.CharField(max_length=255)
email = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class DataReview(models.Model):
comments = models.CharField(max_length=255)
update_author = models.ForeignKey('Author')
creation_date = models.DateTimeField('date data review created')
update_date = models.DateTimeField('date review modified')
def __unicode__(self):
return "%s" % (self.comments)
forms.py corrected
from bamboo.followup.models import *
from django import forms
from django.forms import ModelForm,Form
from django.forms.extras.widgets import SelectDateWidget
import datetime
class DataReviewForm(ModelForm):
def __init__(self, *args, **kwargs):
initial = {
'update_date': datetime.datetime.now(),
'creation_date': datetime.datetime.now(),
'update_author': 1 # not Author.objects.get(id=1)
}
if kwargs.has_key('initial'):
kwargs['initial'].update(initial)
else:
kwargs['initial'] = initial
# Initializing form only after you have set initial dict
super(DataReviewsForm,self).__init__(*args, **kwargs)
self.fields['update_date'].widget.attrs['disabled']=''
self.fields['creation_date'].widget.attrs['disabled']=''
self.fields['update_author'].widget.attrs['disabled'] = ''
class Meta:
model = DataReview
views.py updated
def review(request):
DataReviewsFormSet = modelformset_factory(DataReview, form=DataReviewsForm)
if request.method == 'POST':
formset = DataReviewSet(request.POST)
if formset.is_valid():
# do something with the formset.cleaned_dat
print 'YOUHOU!'
else:
formset = DataReviewSet()
return render_to_response('followup/runs.html', {
"formset": formset,
})
For the core of your question is, as to how to set initial value for ForeignKey field, check the code snippet below.
Note that when you provide initial value to a ForeignKey field, you don't need to pass the object. Pass it the id/pk of that object, and your problem will be solved.
initial = {
'update_date': datetime.datetime.now(),
'creation_date': datetime.datetime.now(),
'update_author': 1 # not Author.objects.get(id=1)
}
if kwargs.has_key('initial'):
kwargs['initial'].update(initial)
else:
kwargs['initial'] = initial
# Initializing form only after you have set initial dict
super(DataReviewsForm,self).__init__(*args, **kwargs)
self.fields['update_date'].widget = forms.HiddenInput()
self.fields['creation_date'].widget = forms.HiddenInput()
self.fields['update_author'].widget = forms.HiddenInput()
A lot cleaner way to do it.
Disabling the fields
As Form exposes 'fields widgets' only after invoking constructor, any updation [be it widget override, or changing attributes of widget], can be done only after that. Or, you can do it in form field definition, which doesn't apply on your case. In short, disabling the field is better after super constructor call.