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
Related
I am trying to make a form which has an option of adding multiple objects at a time. I want it to have a button "add another" - when clicked a new form field would appear for adding additional object. If there was an previous not submitted input I want the form to keep it. Is it possible to use templates tags for this(i.e. django template tags and not javascript)?
Your form would have to be constructed based on some variables passed to it from your POST (or blindly check for attributes). The form itself is constructed every time the view is reloaded, errors or not, so the HTML needs to contain information about how many fields there are to construct the correct amount of fields for validation.
I'd look at this problem the way FormSets work: there is a hidden field that contains the number of forms active, and each form name is prepended with the form index. In fact, you could make a one field FormSet.
https://docs.djangoproject.com/en/dev/topics/forms/formsets/#formsets
Here's one made from scratch - it should give you some ideas. It also answers your questions about passing arguments to __init__ - you just pass arguments to an objects constructor: MyForm('arg1', 'arg2', kwarg1='keyword arg')
### Views
class MyForm(forms.Form):
original_field = forms.CharField()
extra_field_count = forms.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
extra_fields = kwargs.pop('extra', 0)
super(MyForm, self).__init__(*args, **kwargs)
self.fields['extra_field_count'].initial = extra_fields
for index in range(int(extra_fields)):
# generate extra fields in the number specified via extra_fields
self.fields['extra_field_{index}'.format(index=index)] = \
forms.CharField()
def myview(request):
if request.method == 'POST':
form = MyForm(request.POST, extra=request.POST.get('extra_field_count')
if form.is_valid():
print "valid!"
else:
form = MyForm()
return render(request, "template", { 'form': form })
### HTML
form_count = Number($("[name=extra_field_count]").val());
// get extra form count so we know what index to use for the next item.
$("#add-another").click(function() {
form_count ++;
element = $('<input type="text"/>');
element.attr('name', 'extra_field_' + form_count);
$("#forms").append(element);
// build element and append it to our forms container
$("[name=extra_field_count]").val(form_count);
// increment form count so our view knows to populate
// that many fields for validation
})
<form>
<div id="forms">
{{ form.as_p }}
</div>
<button id="add-another">add another</button>
<input type="submit" />
</form>
I have Django template which has one CharField. Users can enter a mobile phone number here and submit it.
The view connected to this template sometimes passes to the said template, a context variable containing a pre-defined phone number {{ number }}.
How do I auto-populate the CharField in the Django template with {{ number }}?
The template code is like so:
<form action="{% url 'user_phonenumber' slug=unique num=decision %}" method="POST">
{% csrf_token %}
<b style="font-size:90%;color:green;">Enter your mobile number:</b><br><br>
{{ form.mobile_number }}<br><br>
<input type="submit" name="number" value="OK"><br>
</form>
If required, forms.py contains the following:
class UserPhoneNumberForm(forms.Form):
mobile_number = forms.CharField(max_length=50)
class Meta:
fields = ("mobile_number")
In views.py, I simply have:
class UserPhoneNumberView(FormView):
form_class = UserPhoneNumberForm
template_name = "get_user_phonenumber.html"
def get_context_data(self, **kwargs):
context = super(UserPhoneNumberView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated():
#some code to determine what number should be
context["number"] = number
return context
I've looked at a few other SO answers. The last one I've linked comes close to solving the problem, but the solution isn't for dynamic values (which is what I need), plus that's for modelforms. The rest don't quite address my particular needs.
I needed to over-ride def get_initial(self): of my FormView to achieve this. I.e. instead of passing a context variable to the template, pass the variable as initial through get_initial() to the related form.
Here's how I did it:
def get_initial(self):
"""
Returns the initial data to use for forms on this view.
"""
if self.request.user.is_authenticated():
#What's the latest chatpicmessage which houses a pic you own
try:
msg = PicMessage.objects.filter(which_pic__owner=self.request.user).latest('sending_time')
self.initial = {'mobile_number': msg.what_number} #initial needs to be passed a dictionary
return self.initial
except:
return self.initial
else:#i.e user is not authenticated
return self.initial
return self.initial
More documentation regarding this can be found here: https://docs.djangoproject.com/es/1.9/ref/forms/fields/#initial
I am writing a writing a webapp that is basically just a form, and it has a button that duplicates a field so that multiple items can be entered. I can't use a SelectMultiple field or any of its variations because there is not a set number of choices to choose from. The user should be able to enter whatever they want into the fields and they must be saved in the model and linked to a record through a manytomany field. Here is a jsfiddle link for demonstration.
HTML
<form>
<label>Field 1
<textarea rows="3"></textarea>
</label>
<label>Multiple Values Possible</label>
<div>
<input type="text">
<select>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<button id="add_div">Add</button>
</form>
JS
add_div = document.getElementById("add_div");
add_div.onclick = function () {
var div = this.previousElementSibling;
var new_div = div.cloneNode(true);
this.parentNode.insertBefore(new_div, this);
return false;
}.bind(add_div);
I cannot figure out how to create the form backend for this. There aren't any field classes that can take in a variable amount of data and validate each one against another field.
What I have tried to do is create a MultiWidget/MultiValueField for the textbox/select dropdown pair, and then subclass my MultiValueField in a class closely following django's ModelMultipleChoiceField. I got stuck trying to get the form field to work with templates, allowing me to add all fields back to the rendered page when rendering with a particular form instance (like how when you use the CheckboxSelectMultiple widget, boxes that are checked in a form instance are rendered checked)
Is there any way to do this and have the ModelForm's save method also save the manytomany fields properly? I know I can override the form's save method and do something like in this stackoverflow question, but I would rather have all the save logic handled by the form fields themselves.
Based on looking at your example jsfiddle, it looks like you don't really need a "Choice Field", what you're looking for are Formsets.
In essence, you would have 2 forms on the page, one which is a normal form and would take care of Field 1, and one which is a Formset which deals with all the many-to-many relations for Field 2
Field2FormSet = formset_factory(Field2ToForm)
Make sure you output the management_form which you can clone with your "add" button.
What you are probably looking for is an inline formset, which can only be used if you are using django models (which you hinted at in your question).
Check out this guide: http://lab305.com/news/2012/jul/19/django-inline-formset-underscore/.
For the lazy, here is a quick example that gets you most of the way there. This app will allow you to continuously add Parent model objects to the database, along with any children that are filled out.
app/models.py
from django.db import models
class ParentModel(models.Model):
parent_field = models.CharField(choices=[(1, 1), (2, 2)])
class ChildModel(models.Model):
parent = models.ForeignKey(ParentModel)
child_field = models.IntegerField(choices=[(1, 1), (2, 2)])
app/views.py
from app import models
from django import forms
from django.forms.models import inlineformset_factory
from django.template import RequestContext, Template
from django.http import HttpResponse
class ParentForm(forms.ModelForm):
class Meta:
model = models.ParentModel
ChildFormSet = inlineformset_factory(models.ParentModel, models.ChildModel)
def test_view(request):
if request.method == "POST":
form = ParentForm(request.POST, request.FILES)
formset = ChildFormSet(request.POST, request.FILES, form.instance)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
else:
pass # Handle validation error
template = Template(
"<form action='<url for view>' method='post'>"
"{% csrf_token %}"
"{{ form.as_p }}"
"<p>{{ formset.as_table }}</p>"
"<input type='submit' value='Submit'/>"
"</form>"
)
context = RequestContext(request, {
"form": ParentForm(),
"formset": ChildFormSet(),
})
return HttpResponse(template.render(context))
What is shown above will only allow you add up to three children (the default number of extra forms the inline form set produces). To add dynamically, you are going to have to add some java script that creates new forms in the form set on the client side. For that, I suggest you look at the guide I posted above since I don't think I can do better job of explaining it.
Thanks to #Kevin and #Thomas for pointing me towards formsets! Here is how I did it:
models.py
from django.db import models
class RelatedField(models.Model):
field1 = models.CharField(max_length=50)
field2 = models.IntegerField(choices=[(x, x) for x in xrange(1, 11)])
class Record(models.Model):
user = models.ForeignKey(User)
field = models.CharField(max_length=20)
relatedA = models.ManyToManyField(RelatedField, related_name='relatedA')
relatedB = models.ManyToManyField(RelatedField, related_name='relatedB')
views.py
def getIndexContext(data):
if data is None:
recordForm = RecordForm()
relatedFormA = RelatedFormSet(queryset=RelatedField.objects.none(), prefix='related-a')
relatedFormB = RelatedFormSet(queryset=RelatedField.objects.none(), prefix='related-b')
else:
recordForm = RecordForm(data)
relatedFormA = RelatedFormSet(data, prefix='related-a')
relatedFormB = RelatedFormSet(data, prefix='related-b')
return {
'form': recordForm,
'relatedA': relatedFormA,
'relatedB': relatedFormB,
'title': 'Index',
}
def index(request):
if request.method == 'GET':
return render(request, 'record/index.html', getIndexContext(None))
else:
context = getIndexContext(request.POST)
form = context['form']
relatedA = context['relatedA']
relatedB = context['relatedB']
if form.is_valid() and relatedA.is_valid() and relatedB.is_valid():
obj = form.save(commit=False)
obj.user_id = request.user
obj.save()
form.save_m2m()
instances = relatedA.save()
obj.relatedA.add(*instances)
instances = relatedB.save()
obj.relatedB.add(*instances)
return HttpResponse('success!')
else:
return render(request, 'record/index.html', context)
And then some javascript that can duplicate the fields in the formsets, and increment the names by one.
I've got some <selects> that I need to populate with some choices that depend on the currently logged in user. I don't think this is possible (or easy) to do from inside the form class, so can I just leave the choices blank and set them in the view instead? Or what approach should I take?
Not sure if this is the best answer, but in the past I have set the choices of a choice field in the init of the form - you could potentially pass your choices to the constructor of your form...
You could build your form dynamically in you view (well, actually i would rather keep the code outside the view in it's own function and just call it in the view but that's just details)
I did it like this in one project:
user_choices = [(1, 'something'), (2, 'something_else')]
fields['choice'] = forms.ChoiceField(
choices=user_choices,
widget=forms.RadioSelect,
)
MyForm = type('SelectableForm', (forms.BaseForm,), { 'base_fields': fields })
form = MyForm()
Obviously, you will want to create the user_choices depending on current user and add whatever field you need along with the choices, but this is a basic principle, I'll leave the rest as the reader exercise.
Considering that you have included the user as a parameter, I would solve this using a custom tag.
In your app/templatetags/custom_tags.py something like this:
#register.simple_tag
def combo(user, another_param):
objects = get_objects(user, another_param)
str = '<select name="example" id="id_example">'
for object in objects:
str += '<option value="%s">%s</option>' % (object.id, object.name)
str += '</select>'
return mark_safe(str)
Then in your template:
{% load custom_tags %}
{% special_select user another_param %}
More about custom tags http://docs.djangoproject.com/en/dev/howto/custom-template-tags/
Django create dynamic forms - It works !!
Forms.py
class MyForm(forms.Form):
""" Initialize form values from views"""
select=forms.BooleanField(label='',required=False)
field_1=forms.CharField(label='',widget=forms.TextInput(attrs= \
{'size':'20','readonly':'readonly',}))
field_2=forms.ChoiceField(widget=forms.Select(), \
choices=((test.id,test.value) for test in test.objects.all()))
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
input=kwargs.get('initial',{})
get_field_1_initial_input_from_views=re.sub("\[|\]|u'|'","",str (input.values()))
# override field_2 choices based on field_1 input
try:
# filter choices
self.fields[‘field_2'].choices=((test.id,test.value) for test in test.objects.filter ( --------)))
except:
pass
Views.py
def views_function(request,val):
"""Dynamically generate input data for formset."""
Initial_data=[]
initial_data.append({'field_1':data.info})
#Initializing formset
MyFormSet=formset_factory(MyForm,extra=0)
formset=MyFormSet(initial=initial_data)
context={'formset':formset}
if request.method == 'POST':
formset=MyFormSet(request.POST,request.FILES)
if formset.is_valid():
# You can work with the formset dictionary elements in the views function (or) pass it to
#Forms.py script through an instance of MyForm
return HttpResponse(formset.cleaned_data)
return render_to_response(‘test.html', context,context_instance=RequestContext(request))
A form will be spitting out an unknown number of questions to be answered. each question contains a prompt, a value field, and a unit field. The form is built at runtime in the formclass's init method.
edit: each questions receives a unique prompt to be used as a label, as well as a unique list of units for the select element.
this seems a case perfect for iterable form fieldsets, which could be easily styled. but since fieldsets - such as those in django-form-utils are defined as tuples, they are immutable... and I can't find a way to define them at runtime. is this possible, or perhaps another solution?
Edit:
formsets with initial_data is not the answer - initial_data merely enables the setting of default values for the form fields in a formset. a list of items can't be sent to the choicefield constructor by way of initial_data.
...unless I'm wrong.
Check out formsets. You should be able to pass in the data for each of the N questions as initial data. Something along these lines:
question_data = []
for question in your_question_list:
question_data.append({'prompt': question.prompt,
'value': question.value,
'units': question.units})
QuestionFormSet = formset_factory(QuestionForm, extra=2)
formset = QuestionFormSet(initial=question_data)
Old question but I am running into a similar problem. The closest thing that I have found so far is this snippet based of a post that Malcom did a couple years ago now.
http://djangosnippets.org/snippets/1955/
The original snippet did not address the template side and splitting them up into fieldsets, but adding each form to its own fieldset should accomplish that.
forms.py
from django.forms.formsets import Form, BaseFormSet, formset_factory, \
ValidationError
class QuestionForm(Form):
"""Form for a single question on a quiz"""
def __init__(self, *args, **kwargs):
# CODE TRICK #1
# pass in a question from the formset
# use the question to build the form
# pop removes from dict, so we don't pass to the parent
self.question = kwargs.pop('question')
super(QuestionForm, self).__init__(*args, **kwargs)
# CODE TRICK #2
# add a non-declared field to fields
# use an order_by clause if you care about order
self.answers = self.question.answer_set.all(
).order_by('id')
self.fields['answers'] = forms.ModelChoiceField(
queryset=self.answers())
class BaseQuizFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
# CODE TRICK #3 - same as #1:
# pass in a valid quiz object from the view
# pop removes arg, so we don't pass to the parent
self.quiz = kwargs.pop('quiz')
# CODE TRICK #4
# set length of extras based on query
# each question will fill one 'extra' slot
# use an order_by clause if you care about order
self.questions = self.quiz.question_set.all().order_by('id')
self.extra = len(self.questions)
if not self.extra:
raise Http404('Badly configured quiz has no questions.')
# call the parent constructor to finish __init__
super(BaseQuizFormSet, self).__init__(*args, **kwargs)
def _construct_form(self, index, **kwargs):
# CODE TRICK #5
# know that _construct_form is where forms get added
# we can take advantage of this fact to add our forms
# add custom kwargs, using the index to retrieve a question
# kwargs will be passed to our form class
kwargs['question'] = self.questions[index]
return super(BaseQuizFormSet, self)._construct_form(index, **kwargs)
QuizFormSet = formset_factory(
QuestionForm, formset=BaseQuizDynamicFormSet)
views.py
from django.http import Http404
def quiz_form(request, quiz_id):
try:
quiz = Quiz.objects.get(pk=quiz_id)
except Quiz.DoesNotExist:
return Http404('Invalid quiz id.')
if request.method == 'POST':
formset = QuizFormSet(quiz=quiz, data=request.POST)
answers = []
if formset.is_valid():
for form in formset.forms:
answers.append(str(int(form.is_correct())))
return HttpResponseRedirect('%s?a=%s'
% (reverse('result-display',args=[quiz_id]), ''.join(answers)))
else:
formset = QuizFormSet(quiz=quiz)
return render_to_response('quiz.html', locals())
template
{% for form in formset.forms %}
<fieldset>{{ form }}</fieldset>
{% endfor %}
I used the trick below to create a dynamic formset. Call the create_dynamic_formset() function from your view.
def create_dynamic_formset(name_filter):
"""
-Need to create the classess dynamically since there is no other way to filter
"""
class FormWithFilteredField(forms.ModelForm):
type = forms.ModelChoiceField(queryset=SomeType.objects.filter(name__icontains=name_filter))
class Meta:
model=SomeModelClass
return modelformset_factory(SomeModelClass, form=FormWithFilteredField)
Here is what I used for a similar case (a variable set of fieldsets, each one containing a variable set of fields).
I used the type() function to build my Form Class, and BetterBaseForm class from django-form-utils.
def makeFurnitureForm():
"""makeFurnitureForm() function will generate a form with
QuantityFurnitureFields."""
furnitures = Furniture.objects.all()
fieldsets = {}
fields = {}
for obj in furnitures:
# I used a custom Form Field, but you can use whatever you want.
field = QuantityFurnitureField(name = obj.name)
fields[obj.name] = field
if not obj.room in fieldsets.keys():
fieldsets[obj.room] = [field,]
else:
fieldsets[obj.room].append(field)
# Here I use a double list comprehension to define my fieldsets
# and the fields within.
# First item of each tuple is the fieldset name.
# Second item of each tuple is a dictionnary containing :
# -The names of the fields. (I used a list comprehension for this)
# -The legend of the fieldset.
# You also can add other meta attributes, like "description" or "classes",
# see the documentation for further informations.
# I added an example of output to show what the dic variable
# I create may look like.
dic = [(name, {"fields": [field.name for field in fieldsets[name]], "legend" : name})
for name in fieldsets.keys()]
print(dic)
# Here I return a class object that is my form class.
# It inherits from both forms.BaseForm and forms_utils.forms.BetterBaseForm.
return (type("FurnitureForm",
(forms.BaseForm, form_utils.forms.BetterBaseForm,),
{"_fieldsets" : dic, "base_fields" : fields,
"_fieldset_collection" : None, '_row_attrs' : {}}))
Here is an example of how dic may look like :
[('fieldset name 1',
{'legend': 'fieldset legend 2',
'fields' ['field name 1-1']}),
('fieldset name 2',
{'legend': 'fieldset legend 2',
'fields' : ['field 1-1', 'field 1-2']})]
I used BetterBaseForm rather than BetterForm for the same reason this article suggests to use BaseForm rather than Form.
This article is interesting even if it's old, and explains how to do dynamic forms (with variable set of fields). It also gives other ways to achieve dynamic forms.
It doesn't explain how to do it with fieldsets though, but it inspired me to find how to do it, and the principle remains the same.
Using it in a view is pretty simple :
return (render(request,'main/form-template.html', {"form" : (makeFurnitureForm())()}))
and in a template :
<form method="POST" name="myform" action=".">
{% csrf_token %}
<div>
{% for fieldset in form.fieldsets %}
<fieldset>
<legend>{{ fieldset.legend }}</legend>
{% for field in fieldset %}
<div>
{% include "main/furniturefieldtemplate.html" with field=field %}
</div>
{% endfor %}
</fieldset>
{% endfor %}
</div>
<input type="submit" value="Submit"/>
</form>