Django Forms choice-like field, without choice limitation - django

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.

Related

How to change input method of Django default form ? from text input to choices

I have below create view,
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ['title','name']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
and in Post create template I just put it as a form tag
<form method="POST" >
{% csrf_token %}
{{ form|crispy }}
</form>
I want name field to be dropdown choice in frontend HTML, What shall I do ?
It is possible by adding choice field in models.py but I can not do that and I have another idea to implement,
The best way to do this in my opinion is to create a form class and pass the form to your view rather than having the view create the form.
Here is what a form could look like
from django import forms
from django.forms.widgets import Select
#also import your models from your app
class PostForm(forms.ModelForm):
class Meta:
# name_choices needs to be a list of tuples that have
# the select option value as the first index, and the option display text
# as the second
name_choices = [(value,option_text),...]
model = Post
fields = ['title','name']
widgets = {'name':Select(choices=name_choices)}

Django Forms - How to link a check box to a boolean field

python 3.6
django 1.11 (haven't updated it yet)
I'm wanting to add a True/False checkbox to a form. I see how to do it on a html page and on the form itself, but the two aren't linking. Whenever I check the True box, the field that represents the checkbox is false.
HTML
<div class='form-group'>
<input type="checkbox" name="donation-repeat" id="donation-repeat_id">
<label for="donation-repeat_id">Repeat my donation (yearly)</label><br>
</div>
Forms.py
class DonationCreateForm(forms.ModelForm):
recurring_donation = forms.BooleanField(required = False,label = 'donation-repeat_id')
class Meta:
model = Donation
fields = ['total','recurring_donation']
def clean_recurring_donation(self):
print('we are cleaning now')
print(self.cleaned_data)
recurring_donation = self.cleaned_data['recurring_donation']
print('recurring_donation1 ', recurring_donation)
return recurring_donation
Views.py
class DonationCreateView(LoginRequiredMixin, CreateView):
form_class = DonationCreateForm
template_name = 'donations/create.html'
def form_valid(self, form):
donation_form = form.save(commit=False)
However, I am always getting this output
recurring_donation1 False
Regardless of whether or not I have checked the box.
What am I doing wrong?
You have to bind your form to the data coming from your request. In your view
form = DonationCreationForm(request.POST)
You must also use tag in your template in addition to submit button.
Give the following a try: remove 'required=False'
good luck!

Django forms. Save data but do not validate until submission

I am new to Django and have been doing lots of reading so perhaps this is a noob question.
We have applications that involve many forms that users fill out along the way. One user might fill out the budget page and another user might fill out the project description page. Along the way any data they input will be SAVED but NOT validated.
On the review page only data is shown and no input boxes / forms. At the bottom is a submit button. When the user submits the application I then want validation to be performed on all the parts / pages / forms of the application. If there are validation errors then the application can not be submitted.
My model fields are mostly marked as blank=True or null=True depending on the field type. Some fields are required but most I leave blank or null to allow the users to input data along the way.
Any advice on best practices or do not repeat yourself is greatly appreciated.
There is an app in django called form wizard. Using it you can split form submission process for multiple steps.
After a lot of learning, playing and reading I think I have figured a few things and will share them here. I do not know if this is right, however it is progress for me.
So first comes the models. Everything needs to accept blank or null depending on the field type. This will allow the end user to input data as they get it:
class exampleModel(models.Model):
field_1 = models.CharField(blank=True, max_length=25)
field_2 = models.CharField(blank=True, max_length=50)
.........
Then we create our model form:
from your.models import exampleModel
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column
class exampleForm(ModelForm):
class Meta:
model = exampleModel
fields = ('field_1','field_2')
def __init__(self, *args, **kwargs):
# DID WE GET A VALIDATE ARGUMENT?
self.validate = kwargs.pop('validate', False)
super(ExampleForm, self).__init__(*args, **kwargs)
# SEE IF WE HAVE TO VALIDATE
for field in self.fields:
if self.validate:
self.fields[field].required = True
else:
self.fields[field].required = False
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.layout = Layout(
Row(
Column('field_1', css_class='col-lg-4 col-md-4'),
Column('field_2', css_class='col-lg-4 col-md-4')
)
)
def clean(self):
cleaned_data = super(ExampleForm, self).clean()
field_1 = cleaned_data.get('field1')
field_2 = cleaned_data.get('field2')
if self.validate and field_2 != field_2:
self.add_error('field_1', 'Field 1 does not match field2')
return cleaned_data
Here is the important part. I've learned a lot about forms and binding. As I mentioned I needed users to be able to fill out forms and not validate the data till the very end. This is my solution which helped me. I could not find a way to bind a form to the model data, so I created a function in my lib called bind_queryset_to_form which looks like this:
def bind_queryset_to_form(qs, form):
form_data = {}
my_form = form()
for field in my_form.fields:
form_data[field] = getattr(qs, field, None)
my_form = form(data=form_data, validate=True)
return my_form
The view:
from your.models import exampleModel
from your.form import exampleForm
from your.lib.bind_queryset_to_form import bind_queryset_to_form
from django.shortcuts import render, get_object_or_404
def your_view(request, pk):
query_set = get_object_or_404(exampleModel, id=pk)
context = dict()
context['query_set'] = query_set
# SAVE THE FORM (POST)
if request.method == 'POST':
form = exampleForm(request.POST, instance=query_set)
form.save()
context['form'] = form
# GET THE DATA.
if request.method == 'GET':
if request.session.get('validate_data'):
# BIND AND VALIDATE
context['form'] = bind_queryset_to_form(query_set, exampleForm)
else:
# NO BIND, NO VALIDATE
context['form'] = exampleForm(instance=query_set)
return render(request, 'dir/your.html', context)
The template:
{% load crispy_forms_tags %}
<div id="div_some_tab">
<form id="form_some_tab" action="{% url 'xx:xx' query_set.id %}" method="post">
{% crispy form form.helper %}
</form>
</div>
What does all the above allow?
I have many views with many data inputs. The user can visit each view and add data as they have it. On the review page I set the flag / session "validate_data". This causes the app to start validating all the fields. Any errors will all be displayed on the review page. When the user goes to correct the errors for the given view the bind_queryset_to_form(query_set, exampleForm) is called binding the form with data from the queryset and highlighting any errors.
I cut out a lot of the exceptions and permission to keep this as transparent as possible (the goat would hate that). Hope this idea might help someone else or someone else might improve upon it.

django dynamic data driven form fields [duplicate]

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

Valid django form not saving

In my view for editing friends I'm checking if form is valid, and then save the form. But somehow the data are not updated. Why my updated form is not saved ? It is 100% valid, since I've checked it earlier.
My form :
class FriendForm(forms.ModelForm):
first_name = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="First name")
last_name = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="Last name")
pid = forms.RegexField(regex=r'^\d{11}', max_length=11 ,widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)))
image = forms.ImageField(label="Image", required=False)
street = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="Street")
number = forms.CharField(widget=forms.TextInput, label="House/flat number")
code = forms.RegexField(regex=r'^\d{2}[-]\d{3}', max_length=6, widget=forms.TextInput(attrs=attrs_dict), label="Postal code")
city = forms.CharField(widget=forms.TextInput, label="City")
The view :
def edit_friend(request, id):
userprofile = UserProfile.objects.get(user=request.user)
friend = get_object_or_404(Friend, id=id)
if friend.friend_of.id!=userprofile.id:
raise Http404
if request.method == 'POST':
form = FriendForm(request.POST, request.FILES, instance=friend)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('user_profile',))
else:
form = FriendForm(instance=friend)
return render_to_response('user/data_operations/edit_friend.html', {
'form':form, 'user':request.user,
}, context_instance=RequestContext(request))
Template :
<form method="post" action="." enctype="multipart/form-data">
<table>
{{ form.as_table }}
<tr>
<td> </td>
<td>
<input type="submit" class="submit" name="submit" value="Save" />
</td>
</tr>
</table>
</form>
I'd need to see the full code for your form to really answer this (can you include it?) but here are some initial thoughts:
Is FriendForm a subclass of django.forms.ModelForm? If so, there's no need to override the save method -- especially since all you're doing here is getting the returned new object, saving it (again), and returning it (again) -- additional processing with no additional benefit.
If FriendForm isn't a subclass of ModelForm, how is it bound to database data? What class is it inheriting from?
UPDATE:
ModelForms aren't connected directly to the database -- they are a shortcut for creating HTML forms for interacting with database models -- i.e. classes inheriting from django.models.Model. You do that by creating a Meta class within your ModelForm.
With a ModelForm, you don't need to manually specify the fields (django does that automatically for you) unless you want to override specific behaviors. You do have to tell django which database Model you're like to use. If you've already defined a database Model, use it; if not, try this:
# in models.py
from django import models
class Friend(models.Model):
first_name = models.CharField( "see <http://docs.djangoproject.com/en/dev/ref/models/fields/> to adjust your syntax" )
... your other fields ...
# in forms.py
from django.forms import ModelForm
from my_project.my_app.models import Friend
class FriendForm(ModelForm):
class Meta:
model = Friend
That's it! Now your FriendForm should work properly. See http://docs.djangoproject.com/en/dev/topics/forms/modelforms/ for more information on using ModelForms.
Alternatively, you don't need to use a ModelForm at all. You could add the following save method to your existing FriendForm:
def save(self):
if self.is_valid():
from myproject.myapp.models import Friend
new_friend = Friend(**self.cleaned_data)
new_friend.save()
return new_friend
... the essence here is that you're importing the Friend model, which encapsulates the database storage behaviors. This is basically what the ModelForm creates automatically for you, but not as robust -- so I recommend using ModelForm.
sometimes, if you add decorators like #transaction.commit_on_success during development, it prevents saving the form if there is even a single error.
I think you may be overriding your model fields with those form fields.
check this. Normally when you have a modelform all you need to do is define a meta class and pass the extra settings you need for that particular form (rendered fields, their widgets, their labels, etc).
You should set action attribute for your form tag. And set url name in urls.py.
Like <form action="{% url 'edit_friend' %}" method="post">
urls.py:
(...'/some-edit-url/', views.edit_friend, name='edit_friend'),