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.
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'm fairly new to python(3.5)/django(1.10), and I've run into the following problem:
I am using Django's generic CreateView to create a model and respective children model.
My objective is to save information about a purchase. A purchase is composed of a Bill and one or many Receipt. To accomplish this, I've created a custom Form (BillForm) with a custom field where user can input coma separated values that will be used to create receipts.
Here is what I have:
models.py
class Bill(models.Model):
""" A given bill whenever an item is purchased."""
number = models.CharField(_('Number'), max_length=20)
purchase_date = models.DateTimeField(_('Purchase Date'))
company = models.ForeignKey(Company, verbose_name=_('Company'),
on_delete=models.DO_NOTHING)
...
def _get_receipts(self):
''' returns all receipts that related to a bill '''
return Receipt.objects.filter(bill = self)
receipts = property(_get_receipts)
class Receipt(models.Model):
""" A receipt confirming a product is in our posession """
number = models.CharField(_('Number'), max_length=20)
bill = models.ForeignKey(Bill, verbose_name=_('Bill'),
on_delete=models.DO_NOTHING)
urls.py:
url(_(r'^create_purchase/$'), views.ObjectCreateView.as_view(
model=Bill,
form_class=BillForm
),
name="create_purchase"),
forms.py:
class MultipleReceiptsField(forms.Field):
''' A custom field to store a list of coma separated receipts '''
def to_python(self, value):
''' Normalize data to a set of receipts numbers '''
if not value:
return set()
return set(v.strip() for v in value.split(',') if v.strip())
def validate(self, value):
''' check if the values passed are less than 20 char long (limit for
model). '''
super(MultipleReceiptsField, self).validate(value)
# had made the following a tuple, then updated to list and worked
invalid_receipts = [r for r in value if len(r) > 20]
if invalid_receipts:
# EXCEPTION THROWN HERE BUT NOT PROPAGATED
raise ValidationError(
[ValidationError(_("Length of receipt %(r) is too large (max 20 characters)"),
code=INVALID_PARAMS, params={'r':int(r)}) # it was params={r:r} should have used 'r'
for r in invalid_receipts]
)
class BillForm(forms.ModelForm):
''' A form used to create a Purchase Bill. '''
receipts = MultipleReceiptsField(label=_("Receipts"), widget=forms.Textarea)
def __init__(self, *args, **kwargs):
''' constructor used to filter the companies. '''
# executes
super(BillForm, self).__init__(*args, **kwargs)
self.fields['company'].queryset =\ Company.objects.filter(is_provider=True).filter(is_active=True)
def save(self, commit=True):
''' createe receipts when saving a bll '''
# save logic | NEVER EXECUTES...
# for each elementt in receipts, create a receipt object and
# set its bill value to the bill that we just created
bill = super(BillForm, self).save(commit=False)
receipts_cd = self.cleaned_data["receipts"]
...
return bill
class Meta:
model = Bill
fields = ('company', "number", "currency", "price", "receipts")
view.py:
class ObjectCreateView(IsInPurchaseGroup, CreateView):
model = None
form_class = None
#fields = "__all__"
template_name = "purchases/object_form.html"
def get_context_data(self, **kwargs):
context = super(ObjectCreateView, self).get_context_data(**kwargs)
context["title"] = str(ACTIONS[CREATE_ACTION]) + " " +\
str(self.model._meta.verbose_name)
context["button"] = ACTIONS[CREATE_ACTION]
return context
def get_success_url(self):
next_url, kwargs = get_next_url(self.request)
if not next_url:
# works because the pluralization of both models end with s
next_url =\
"purchases:{}s".format((str(self.model).split(".")[-1])[:-2])
# if i am creating a bill, then i must navigate to
# create_purchased_products in the warehouse department.
if self.model == Bill:
next_url = "warehouse:create_purchased_products"
kwargs = {"receipts" : self.object.receipts}
return reverse_lazy(next_url, kwargs=kwargs)
object_form.html
{% extends "purchases/base.html" %}
{% load i18n %}
{% block body %}
<h3>{{ title }}</h3>
{% if error_message %}
<p><strong>{{ error_message }}</strong></p>
{% endif %}
<form action="" method="post">{% csrf_token %}
<table>{{ form.as_table }}</table>
<div class="buttons">
<input type="Submit" value="{{ button }}"/>
</div>
</form>
{% endblock %}
The problem:
whenever a form_class is given on the url, the following functions don't get executed:
BillForm.save()
ObjectCreateView.get_success_url()
If I omit the form_class on the url and set variable fields = "__all__" (simply creating an object of my Bill model), then the method ObjectCreateView.get_success_url() gets called.
My question
Why the BillForm.save() nor the ObjectCreateView.get_success_url() don't get executed? I know there has to be something wrong with the BillForm, but I can't seem to understand what...
Any help is greatly appreciated.
Update
Uploaded object_form.html and update forms.py
There is no exception being thrown. After clicking on Submit, a POST gets sent to the server. On the client (the browser), no changes happen (form remains with already entered data).
Found my errors; but I'm still unsure why it happens.
I had some exceptions being thrown in MultipleReceiptsField.validate(), but they didn't get propagated (when I submited the form, I didn't get exception, form just kept on posting).
After exception was fixed (saw that when placing invalid input will raise a validation error) and tried to submit with correct data, form kept on reposting.
Then I updated the invalid_receipts variable from being a tuple to a list and it began working
Am I missing some subtle difference between tuple and lists?
forms.py - not working:
class MultipleReceiptsField(forms.Field):
''' A custom field to store a list of coma separated receipts '''
def to_python(self, value):
''' Normalize data to a set of receipts numbers '''
if not value:
return set()
return set(v.strip() for v in value.split(',') if v.strip())
def validate(self, value):
''' check if the values passed are less than 20 char long (limit for
model). '''
super(MultipleReceiptsField, self).validate(value)
invalid_receipts = (r for r in value if len(r) > 20)
if invalid_receipts:
raise ValidationError(
[ValidationError(_("Length of recipt %(r) is too large (max 20 characters)"),
code=INVALID_PARAMS, params={'r':int(r)})
for r in invalid_receipts]
)
forms.py - working:
lass MultipleReceiptsField(forms.Field):
''' A custom field to store a list of coma separated receipts '''
def to_python(self, value):
''' Normalize data to a set of receipts numbers '''
if not value:
return set()
return set(v.strip() for v in value.split(',') if v.strip())
def validate(self, value):
''' check if the values passed are less than 20 char long (limit for
model). '''
super(MultipleReceiptsField, self).validate(value)
invalid_receipts = [r for r in value if len(r) > 20]
if invalid_receipts:
raise ValidationError(
[ValidationError(_("Length of receipt %(r) is too large (max 20 characters)"),
code=INVALID_PARAMS, params={'r':int(r)})
for r in invalid_receipts]
)
I think problem in method save. Method save return instance of object. If cant save it return None. Your method return None all time. Create view check result of form save after call other methods. If it see none poccess aborted. You need return some instance or call super.save like this:
def save(self, commit=True):
inst = super(BillForm, self).save(commit=False)
#addition logic, all what you need.
return inst
Or you need override valid method on your view class, without call super().valid() and override save method in form.
Sorry, my english very bad.
I've got a relatively standard RegistrationForm that looks like this:
class RegisterForm(forms.Form):
username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'username'}), initial='')
email = forms.EmailField(widget=forms.TextInput(attrs={'placeholder': 'email'}), initial='')
password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'password'}), initial='')
password_repeat = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'retype password'}), initial='')
How could I create a clean method that returns an error when a user forgets to fill in one or more fields? (ie. "You forgot to fill in the email field")
I've tried the following two options in my clean() method (I'll use the password and password_repeat fields as examples):
password = self.cleaned_data['password']
password_repeat = self.cleaned_data['password_repeat']
# initial values are set to '' for all fields, see above.
if password == '':
raise forms.ValidationError("You forgot to type in a password.")
elif password_repeat == '':
raise forms.ValidationError("You forgot to retype your password.")
The first option returns:
KeyError at /homepage/
'password'
try:
password = self.cleaned_data['password']
password_repeat = self.cleaned_data['password_repeat']
except KeyError(password):
raise forms.ValidationError("You forgot to fill in the password field.")
The second option returns:
UnboundLocalError at /homepage/
local variable 'password' referenced before assignment
Bonus points if you can provide a solution that allows for the remaining fields to be checked as well (so that I can return an form bound to the data the user successfully submitted).
You can use the required property available for all Field types, which automatically does this type of validation. So your code would look like:
class RegisterForm(forms.Form):
username = forms.CharField(
widget = forms.TextInput(attrs = {'placeholder': 'username'}),
required = True)
email = forms.EmailField(
widget = forms.TextInput(attrs = {'placeholder': 'email'}),
required = True)
password = forms.CharField(
widget = forms.PasswordInput(attrs = {'placeholder': 'password'}),
required = True)
password_repeat = forms.CharField(
widget = forms.PasswordInput(attrs = {'placeholder': 'retype password'}),
required = True)
Note: I think you can leave out those initial = '' parameters as well, as shown above.
I'm actually not sure why you're getting the errors you mentioned in your question, perhaps you could post the relevant code from your views.py? It could be because you need to return cleaned_data at the end of any clean method you implement.
I would also just say that your use of the clean method is not quite right. If you refer to this page of the documentation on form and field validation you see that to validate a single field you use the specific clean_<fieldname> method e.g. clean_password_repeat. Using the clean method is appropriate when validation involves multiple field simultaneously, one example of this you may like to use is checking the inputs for the two password fields match.
class RegisterForm(forms.Form):
# field definitions (above)
def clean(self):
password = self.cleaned_data['password']
password_repeat = self.cleaned_data['password_repeat']
if password != password_repeat:
raise forms.ValidationError(u"Passwords do not match.")
return cleaned_data
Note: Code is not tested.
I hope this was helpful!
Since django 1.2 it is able to write validation code on model. I allways write business rules in model side:
This is your model:
from django.db import models
class Issue(models.Model):
....
def clean(self):
rules.Issue_clean(self)
#I write business rules into another file ...
def Incidencia_clean( instance ):
import datetime as dt
errors = {}
#some business rules:
if not instance.dia_incidencia:
errors.setdefault('b1',[]).append(u'Falten...ranja')
if not instance.franja_incidencia:
errors.setdefault('b2',[]).append(u'Falten..nja')
if instance.dia_incidencia < ( dt.date.today() +
dt.timedelta( days = -7) ):
errors.setdefault('b3',[]).append(u'''No es ... setmana)''')
if instance.getDate() > datetime.now():
errors.setdefault('b4',[]).append(u'''Encara ...itzat.''')
if len( errors ) > 0:
raise ValidationError(errors)
In template:
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
{{error}}
{% endfor %}
{% endif %}
I prefer to write business rule one time in model than repeat rules into each form.
Here is a snippet that steps through the keys in a dict and raises an exception if any of them are mapped to the empty string or any other False-like value. The exception tells you which key in the dict was missing an entry.
for key in self.cleaned_data:
if not self.cleaned_data[key]:
raise forms.ValidationError("You didn't fill in the {} form".format(key))
If you exclusively want to test for empty strings (and not False, 0, [], etc), change if not self.cleaned_data[key]: to if self.cleaned_data[key] == '':.
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'm working on something like an online store. I'm making a form in which the customer buys an item, and she can choose how many of these item she would like to buy. But, on every item that she buys she needs to choose what its color would be. So there's a non-constant number of fields: If the customer buys 3 items, she should get 3 <select> boxes for choosing a color, if she buys 7 items, she should get 7 such <select> boxes.
I'll make the HTML form fields appear and disappear using JavaScript. But how do I deal with this on my Django form class? I see that form fields are class attributes, so I don't know how to deal with the fact that some form instance should have 3 color fields and some 7.
Any clue?
Jacob Kaplan-Moss has an extensive writeup on dynamic form fields:
http://jacobian.org/writing/dynamic-form-generation/
Essentially, you add more items to the form's self.fields dictionary during instantiation.
Here's another option: how about a formset?
Since your fields are all the same, that's precisely what formsets are used for.
The django admin uses FormSets + a bit of javascript to add arbitrary length inlines.
class ColorForm(forms.Form):
color = forms.ChoiceField(choices=(('blue', 'Blue'), ('red', 'Red')))
ColorFormSet = formset_factory(ColorForm, extra=0)
# we'll dynamically create the elements, no need for any forms
def myview(request):
if request.method == "POST":
formset = ColorFormSet(request.POST)
for form in formset.forms:
print "You've picked {0}".format(form.cleaned_data['color'])
else:
formset = ColorFormSet()
return render(request, 'template', {'formset': formset}))
JavaScript
<script>
$(function() {
// this is on click event just to demo.
// You would probably run this at page load or quantity change.
$("#generate_forms").click(function() {
// update total form count
quantity = $("[name=quantity]").val();
$("[name=form-TOTAL_FORMS]").val(quantity);
// copy the template and replace prefixes with the correct index
for (i=0;i<quantity;i++) {
// Note: Must use global replace here
html = $("#form_template").clone().html().replace(/__prefix_/g', i);
$("#forms").append(html);
};
})
})
</script>
Template
<form method="post">
{{ formset.management_form }}
<div style="display:none;" id="form_template">
{{ formset.empty_form.as_p }}
</div><!-- stores empty form for javascript -->
<div id="forms"></div><!-- where the generated forms go -->
</form>
<input type="text" name="quantity" value="6" />
<input type="submit" id="generate_forms" value="Generate Forms" />
you can do it like
def __init__(self, n, *args, **kwargs):
super(your_form, self).__init__(*args, **kwargs)
for i in range(0, n):
self.fields["field_name %d" % i] = forms.CharField()
and when you create form instance, you just do
forms = your_form(n)
it's just the basic idea, you can change the code to whatever your want. :D
The way I would do it is the following:
Create an "empty" class that inherits from froms.Form, like this:
class ItemsForm(forms.Form):
pass
Construct a dictionary of forms objects being the actual forms, whose composition would be dependent on the context (e.g. you can import them from an external module). For example:
new_fields = {
'milk' : forms.IntegerField(),
'butter': forms.IntegerField(),
'honey' : forms.IntegerField(),
'eggs' : forms.IntegerField()}
In views, you can use python native "type" function to dynamically generate a Form class with variable number of fields.
DynamicItemsForm = type('DynamicItemsForm', (ItemsForm,), new_fields)
Pass the content to the form and render it in the template:
Form = DynamicItemsForm(content)
context['my_form'] = Form
return render(request, "demo/dynamic.html", context)
The "content" is a dictionary of field values (e.g. even request.POST would do).
You can see my whole example explained here.
Another approach: Rather than breaking the normal field initialization flow, we can override fields with a mixin, return an OrderedDict of dynamic fields in generate_dynamic_fields which will be added whenever its set.
from collections import OrderedDict
class DynamicFormMixin:
_fields: OrderedDict = None
#property
def fields(self):
return self._fields
#fields.setter
def fields(self, value):
self._fields = value
self._fields.update(self.generate_dynamic_fields())
def generate_dynamic_fields(self):
return OrderedDict()
A simple example:
class ExampleForm(DynamicFormMixin, forms.Form):
instance = None
def __init__(self, instance = None, data=None, files=None, auto_id='id_%s', prefix=None, initial=None,
error_class=ErrorList, label_suffix=None, empty_permitted=False, field_order=None,
use_required_attribute=None, renderer=None):
self.instance = instance
super().__init__(data, files, auto_id, prefix, initial, error_class, label_suffix, empty_permitted, field_order,
use_required_attribute, renderer)
def generate_dynamic_fields(self):
dynamic_fields = OrderedDict()
instance = self.instance
dynamic_fields["dynamic_choices"] = forms.ChoiceField(label=_("Number of choices"),
choices=[(str(x), str(x)) for x in range(1, instance.number_of_choices + 1)],
initial=instance.initial_choice)
return dynamic_fields