Hi I have a site which outputs a user results as a table of parts and quantities. User interaction on the page can result in a variable number of rows (parts) in the table.
I need to save this list of parts against the user, so I believe formsets are the best way to handle this:
forms.py
class UserBuildForm(forms.Form):
part = forms.CharField()
part_quantity = forms.IntegerField()
views.py
from django.forms.formsets import formset_factory
from tool.forms import UserBuildForm
def my_view(request):
...
UserBuildFormSet = formset_factory(UserBuildForm)
formset = UserBuildFormSet()
...
My idea is to wrap the form fields in the table html so that a parts column and quantity column are actually inputs (though only the quantity should be user-editable). This way each row is actually a form.
Now, my ajax function works by appending and removing rows to the table.Can I add extra form fields as just plain html?
How should I handle these dynamic number of forms? I know you can specify the number of formsets using extra but the number of forms I will need to validate are unknown.
Also - is this the best approach to this typical method of saving user product data?
Any help much appreciated.
You can add/delete form fields using jquery
Number of forms is stored in hidden input "id_formname_TOTAL-FORMS
Just iterate over forms in views.py to handle data:
for form in formset.forms:
form.save()
Here is excellet tutorial, which show you how to handle with adding/deleting form fields (jquery):
http://stellarchariot.com/blog/2011/02/dynamically-add-form-to-formset-using-javascript-and-django/
Related
I'm working in an existing codebase that uses Django Material. There is a CreateView defined with a Django Material Layout:
class OurModelCreateView(LayoutMixin, CreateView):
model = OurModel
layout = Layout(
Row('field1', 'field2', 'field3'),
Row(...)
)
This view is getting lots of spam signups and so needs to have a captcha. I use Django Recaptcha, and I've set up a number of captchas in the past. However, I've never set one up without using a ModelForm. If I create a Django model form and define the captcha field in the form as I've always done:
from captcha.fields import ReCaptchaField
from captcha.widgets import ReCaptchaV3
class OurModelForm(ModelForm):
captcha = ReCaptchaField(widget=ReCaptchaV3)
class Meta:
model = OurModel
exclude = ()
and then specify form_class = OurModelForm on the CreateView, the following error is raised by ModelFormMixin.get_form_class(): "Specifying both 'fields' and 'form_class' is not permitted". This error is being raised because, though I've not explicitly specified fields, Django Material's LayoutMixin defines fields: https://github.com/viewflow/django-material/blob/294129f7b01a99832a91c48f129cefd02f2fe35f/material/base.py (bottom of the page)
I COULD drop the Material Layout() from the CreateView, but then that would mean having to create an html form to render the Django/Django Material form - less than desirable as there are actually several of these CreateViews that need to have a captcha applied.
So I think that the only way to accomplish what I'm after is to somehow dynamically insert the captcha field into the form.
I've dynamicaly inserted fields into Django forms in the past by placing the field definition in the __init__() of the Django form definition, but I can't figure out what to override in either CreateView (or the various mixins that comprise CreateView) or Django Material's LayoutMixin in order to dynamically insert the captcha field into the form. The following several attempts to override get_form and fields in order to dynamically insert the captcha field do not work:
On the CreateView:
def get_form(self, form_class=None):
form = super(OurModelCreate, self).get_form(form_class)
form.fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
return form
def fields(self):
fields = super().fields(*args, **kwargs)
fields['captcha'] = ReCaptchaField(widget=ReCaptchaV3)
return [field.field_name for field in fields
# fields is actually a list, so trying the following too, but it doesn't include the ReCaptchaField(widget=ReCaptchaV3) anywhere at this point
def fields(self):
fields = super().fields(*args, **kwargs)
fields.append('captcha')
return fields
Any help would be greatly appreciated.
Following up on the comment from #Alasdair above which pointed me to the answer, I solved this problem by removing Django Material's LayoutMixin from CreateView, creating a Django form with the captcha field defined, and then adding to CreateView the form_class for the Django form. Also see my last comment above. It was counterintuitive to me until I looked again at the code after #Alasdair's second comment: the use of LayoutMixin on the CreateView isn't necessary for the layout = Layout(...) on the CreateView to work.
In an my model, I've the following
--- models.py ---
class A(models.Model):
my_Bs = models.ManyToManyField('B', through='AlinksB')
...
class B(models.Model):
...
class AlinksB(models.Model):
my_A = models.ForeignKey(A)
my_B = models.models.ForeignKey(B)
order = models.IntegerField()
So is the corresponding admin (A admin view has an inline to link B instances, and I prepared the required to custom this inline's formset and forms):
--- admin.py ---
class AlinksBInlineForm(forms.ModelForm):
class Meta:
model = AlinksB
class AlinksBInlineFormset(forms.models.BaseInlineFormSet): # there also is a BaseModelFormset
form = AlinksBInlineForm
class AlinksBInline(admin.TabularInline):
formset = AlinksBInlineFormset
model = AlinksB
class AAdmin(admin.ModelAdmin):
form = AForm
inlines = (AlinksBInline,)
...
class BAdmin(admin.ModelAdmin):
...
Now to custom the forms validation, nothing difficult: just override the "clean" method of the form object. If you want many different forms in the formset, I think you just have to change some manually in the "init" method of the formset. But what about programatically validating all the forms when we clean the formset, and that only under some conditions.
In my case: how to automatically set the "order" field (in the inline of A admin view) with an autoincrement if all the orders (inline rows to remove excluded) are empty ?!
I just spent a lot of time Googling about trying to perform automatic form cleaning during a formset validation in Django Framework. After a few days a couldn't figure a solution so I started looking right into Django's source code to see how work fields, widgets, forms and formsets.
Here is what I understood:
-All the data POSTed by the user when he submits the formset it stored in the "data" attribute of the formset. This attribute is very ugly and cannot be directly used.
- The form is just a wrapper for fields (it calls all the fields' clean methods and fill error buffers, and only a few more)
-The form fields have a widget. This widget allow getting back the field's raw value from the "data" attribute of the formset
form.add_prefix('field name') # returns the 'field prefix', the key of formset.data used to retrieve the field's raw value
form.fields['field name'].widget.value_from_datadict(form.data, form.files, 'field prefix') # returns the raw value
-The form fields also have a method to transform the raw value into a right python value (in my case: order is an integer, or None if the field has been left empty)
form.fields['field name'].to_python(raw_value) # returns a value with the right type
-You can change the value of one of the fields from the formset with the following code
form.data.__setitem__('field prefix', value) # code to update an iterable knowing the key to change
-Once you have modified the fields value, you can call the "full_clean" method of the forms to retry cleaning them (this will remove the previous errors).
-Once you have validated again the forms, you can retry validating the formset with its "full_clean" method too. But take care to avoid infinite loops
-The forms clean data can only be used has a read-only data, to add more error messages in the form or the formset
An other solution would be to manually change the "form.clean_data" attribute, and clean the formset.errors and all the form.errors
Hope it could help somebody in the same situation as me !
Ricola3D
I'd like to create a form that when viewed, the user's favorite fruits are queried from the database and displayed as follows:
<select size="4">
<option selected>Apples</option>
<option>Bananas</option>
<option>Oranges</option>
<option>Watermelon</option>
</select>
The view that uses the form will:
Get the user object.
Query the database for the user's favorite fruits. (Each is a separate object of the Fruit model.)
Load the form with the fruit choices collected in (2).
I was considering using the ChoiceField, but it looks like you cannot load the list of choices into the form dynamically, at least in a straightforward manner. Am I better off skipping the form and generating the code directly at the template? Or is there a way to load the form's ChoiceField with the user items at the view?
Also, are there any general rules of thumb that dictate where it's easier to build a form using the django form fields vs generating the form code at the template?
I found the answer in this stack overflow topic. The trick is to override the form __init__() so that it accepts a new keyword argument, which in this case is the user.
views.py snippet
context = RequestContext(request)
user = User.objects.get(username=context['user'])
form = forms.FruitForm(user=user)
forms.py snippet
from django import forms
class FruitForm(forms.Form):
fruits = forms.ModelChoiceField(queryset=Fruit.objects.all())
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(FruitForm, self).__init__(*args, **kwargs)
if user:
self.fields['fruits'].queryset = Fruit.objects.filter(user=user)
It's not that difficult. You can accomplish this easily using a modelform.
See: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/
One of the strengths of the Django framework is it's form handling and validation methods. So if possible, it always better for you to use Django forms or model forms.
Create a Form or a ModelForm that will be used in you view. The differnce between the two classes is the the ModelForm is built to closely resemble a database model defined in your models.py file where a Form can have custom attributes.
from django.forms import ModelForm
class FruitForm(ModelForm):
class Meta:
model = User
fields = ('favorite-fruits', )
one of the forms I need is a composite of simple fields (say "Department", "Building" and "RoomNumber"), and of dynamically generated pairs of fields (say "Name" and "Email"). Ideally, editing the contents of the simple fields and adding/removing dynamic field pairs would be done on a single form.
Code-wise, I'm wondering if trying to embed a Formset (of a form with the two dynamic fields) as a field in an ordinary form is a sensible approach or if there's another best practice to achieve what I'd like to accomplish.
Many thanks for any advice on these matters,
I'm not sure where the idea that you need to "embed a Formset as a field" comes from; this sounds like a case for the standard usage of formsets.
For example (making a whole host of assumptions about your models):
class OfficeForm(forms.Form):
department = forms.ModelChoiceField(...
room_number = forms.IntegerField(...
class StaffForm(forms.Form):
name = forms.CharField(max_length=...
email = forms.EmailField(...
from django.forms.formsets import formset_factory
StaffFormSet = formset_factory(StaffForm)
And then, for your view:
def add_office(request):
if request.method == 'POST':
form = OfficeForm(request.POST)
formset = StaffFormSet(request.POST)
if form.is_valid() && formset.is_valid():
# process form data
# redirect to success page
else:
form = OfficeForm()
formset = StaffFormSet()
# render the form template with `form` and `formset` in the context dict
Possible improvements:
Use the django-dynamic-formset jQuery plugin to get the probably-desired "add an arbitrary number of staff to an office" functionality without showing users a stack of blank forms every time.
Use model formsets instead (assuming the information you're collecting is backed by Django models), so you don't have to explicitly specify the field names or types.
Hope this helps.
For my project I need many "workflow" forms. I explain myself:
The user selects a value in the first field, validates the form and new fields appear depending on the first field value. Then, depending on the others fields, new fields can appear...
How can I implement that in a generic way ?
I think the solution you are looking for is django form wizard
Basically you define separate forms for different pages and customize the next ones based on input in previous screens, at the end, you get all form's data together.
Specifically look at the process step advanced option on the form wizard.
FormWizard.process_step()
"""
Hook for modifying the wizard's internal state, given a fully validated Form object. The Form is guaranteed to have clean, valid data.
This method should not modify any of that data. Rather, it might want to set self.extra_context or dynamically alter self.form_list, based on previously submitted forms.
Note that this method is called every time a page is rendered for all submitted steps.
The function signature:
"""
def process_step(self, request, form, step):
# ...
If you need to only modify the dropdown values based on other dropdowns within the same form, you should have a look at the implemented dajaxproject
I think it depends on the scale of the problem.
You could write some generic JavaScript that shows and hides the form fields (then in the form itself you apply these css classes). This would work well for a relatively small number showing and hiding fields.
If you want to go further than that you will need to think about developing dynamic forms in Django. I would suggest you don't modify the ['field'] in the class like Ghislain suggested. There is a good post here about dynamic forms and it shows you a few approaches.
I would imagine that a good solution might be combining the dynamic forms in the post above with the django FormWizard. The FormWizard will take you through various different Forms and then allow you to save the overall data at the end.
It had a few gotchas though as you can't easily go back a step without loosing the data of the step your on. Also displaying all the forms will require a bit of a customization of the FormWizard. Some of the API isn't documented or considered public (so be wary of it changing in future versions of Django) but if you look at the source you can extend and override parts of the form wizard fairly easily to do what you need.
Finally a simpler FormWizard approach would be to have say 5 static forms and then customize the form selection in the wizard and change what forms are next and only show the relevant forms. This again would work well but it depends how much the forms change on previous choices.
Hope that helps, ask any questions if have any!
It sounds like you want an AJAXy type solution. Checkout the Taconite plugin for jQuery. I use this for populating pulldowns, etc. on forms. Works very nicely.
As for being "generic" ... you might have standard methods on your container classes that return lists of children and then have a template fragmen t that knows how to format that in some 'standard' way.
Ok, I've found a solution that does not use ajax at all and seems nice enough to me :
Create as many forms as needed and make them subclass each other. Put an Integer Hidden Field into the first one :
class Form1(forms.Form):
_nextstep = forms.IntegerField(initial = 0, widget = forms.HiddenInput())
foo11 = forms.IntegerField(label = u'First field of the first form')
foo12 = forms.IntegerField(label = u'Second field of the first form')
class Form2(Form1):
foo21 = forms.CharField(label = u'First field of the second form')
class Form3(Form2):
foo31 = forms.ChoiceField([],
label=u'A choice field which choices will be completed\
depending on the previous forms')
foo32 = forms.IntegerField(label = u'A last one')
# You can alter your fields depending on the data.
# Example follows for the foo31 choice field
def __init__(self, *args, **kwargs):
if self.data and self.data.has_key('foo12'):
self.fields['foo31'].choices = ['make','a','nice','list',
'and you can','use your models']
Ok, that was for the forms now here is the view :
def myview(request):
errors = []
# define the forms used :
steps = [Form1,Form2,Form3]
if request.method != 'POST':
# The first call will use the first form :
form = steps[0]()
else:
step = 0
if request.POST.has_key('_nextstep'):
step = int(request.POST['_nextstep'])
# Fetch the form class corresponding to this step
# and instantiate the form
klass = steps[step]
form = klass(request.POST)
if form.is_valid():
# If the form is valid, increment the step
# and use the new class to create the form
# that will be displayed
data = form.cleaned_data
data['_nextstep'] = min(step + 1, len(steps) - 1)
klass = steps[data['_nextstep']]
form = klass(data)
else:
errors.append(form.errors)
return render_to_response(
'template.html',
{'form':form,'errors':errors},
context_instance = RequestContext(request))
The only problem I saw is that if you use {{form}} in your template, it calls form.errors and so automagically validates the new form (Form2 for example) with the data of the previous one (Form1). So what I do is iterate over the items in the form and only use {{item.id}}, {{item.label}} and {{item}}. As I've already fetched the errors of the previous form in the view and passed this to the template, I add a div to display them on top of the page.