How to implement a sequence of forms - django

New to Django here. I want to implement two forms in a sequence, where the second form depends on the saving of the first form. For example: View_A implements GET / POST methods of form A. Once Form A is submitted and saved, it will generate a unique ID. Form A also has a radio button with two choices (B and C). Depending on the choice made, I need to present the user with a second form B or a form C, where the unique ID from form A is filled automatically. The prerequisite for forms B and C is that the unique ID on form A is present. I can't use AJAX, because the form A needs to be saved first.
My problem is, as soon as I hit the submit button of form A, it becomes a POST request. I can generate the unique ID and display it in the returned html template. However, it's still the same view and I don't know how the display of next form. The display of form B/C should ideally be a GET request. But I am in the POST request.
I hope this is a clear enough question.
Thanks

You would have to implement three views, one for each form. When the users saves form A, you check that the form is valid and generate the identifier. After that, just return a HttpRedirect. If the identifier is used for the process, you also give it as a parameter to the next view.
def my_view(request):
# Construct your form from the POST data
if form.is_valid():
return redirect('next-form-view', identifier=your-generated-identifier)
Documentation about the redirect shortcut can be found here.

Related

Django Forms: dealing with un-validated request.POST

In short: I have a model with several fields, and on this model is a foreign-key field connecting it to to a User instance. Then, I have a model-form(1) that generates HTML for this model's fields, and that with a custom ModelChoiceField-instance that presents UserProfile-instances instead of User-instances. To validate the submitted information, I have a form (regular form) that validates that a UserProfile-instance was submitted. If it was, I update request.POST (through a copy of it), and validate against a second model-form(2) that exactly mirrors the underlying model.
This all means that I first have to pass a request.POST with a lot of un-validated information into a form that only validates part of it, then updated a copy of a partially validated request.POST, edit it, and then finally validate it again.
Is all of this safe? Is it considered bad practice/unsafe to pass a request.POST with a lot of information into a form that only validates one part of it? Additionally, is it safe to make a copy of a request.POST that is only partially validated - and then update it?
To what degree can you work with objects containing unsafe information, such as request.POST, without it becoming a problem?
Thank you!

Form doesn't validate when passing form value to the next view

In my index view I have a ModelChoiceField which allows me to choose various equipments.
When I have submitted my choice, I keep the selected value in a variable like this:
if form.is_valid():
form.save()
request.session["eq"] = form.cleaned_data['equipment']
Then I am redirected to a new view(reservation) in which I have a form with three fields: "equipment", "date" and "reserved_by". Since I have already chosen the equipment i want in the previous view I want it to be filled in automatically. I managed this by doing the following in my reservation view:
form = ReservationForm(initial={'equipment': request.session.get('eq')})
So when I run my site, the equipment field in the reservation view does get automatically filled in, but now the form suddenly won't validate(form.is_valid is false).
Been struggling with this for a while now so any help would be highly appreciative.
This form from your question is unbound.
form = ReservationForm(initial={'equipment': request.session.get('eq')})
Unbound forms are never valid. To get a valid form, you need to bind it to post or get data.

Django formset - validate one of the forms, not all

I have a formset that generates two forms that have different initial values. When the user submits the form, they're only going to fill out one of them, not both. These initial values get populated in the view, which (I think) means FormSet can't figure out if they've changed or not, so default validation fails. But really, all I care about is if one of them is valid. I'd like to take that valid form and process it. What's the best way to go about this?
Not only formset.is_valid() exists, but also
for form in formset.forms:
if form.is_valid():
..
should work.

Django ModelForm Instantiation after Form Creation

I have a form that consists of a response for each entry in another model. At the time the form is generated the response to each item may or may not exist. I need a form that allows me to update the response if it exists and create it with the form post data if it doesn't exist.
Currently I am iterating through a range and creating my forms with the post data:
forms = [SpecialNoteForm(request.POST, prefix=str(x), ) for x in rang(1,3)]
I am doing this because I don't know how else to access the form data cleanly in order to identify the object that the form should be instantiated with. I tried doing something like this after the forms list was created because i can then access the form data:
for form in forms:
try:
instance = SpecialNote.objects.get(flag=form["flag"].data, host=form["host"].data)
form.instance = instance
form.save()
The errors on the form persist after I do this, however. I need a way of accessing the data I need to instantiate the object at the time of form creation or a way of re-evaluating the form after i've attached an instance to it.
EDIT
I ran into the same problem with model formsets as I did with my initial approach--I don't know how to instantiate the forms while at the same time allowing for intial values on forms that don't have an instance. I don't want to create all of the model instances before hand because it is import whether or not the user has submitted these with the required fields filled in.
My current approach is still using the model forms:
forms = []
for n in form_range(request.POST): # calculates number of forms based on post data
try:
instance = SpecialNote.objects.get(flag=request.POST.get('%s'%n+'-flag'), host=request.POST.get('%s'%n+'-host'))
except:
instance = None
forms.append(SpecialNoteForm(request.POST, prefix=str(n), instance=instance))
for form in forms:
if form.is_valid():
form.save()
In summary, the problem with formsets is I don't know how to properly instantiate the forms without having them be queriable, i.e. already in the database. The problem with using regular model forms and a prefix is that getting the objects that i need to instantiate them with is messy (as you can see from my current approach). I'm looking for a solution to either of these two problems.
Multiple identical model forms on one page is what model formsets are for. They should take care of all of those issues.

How to make a "workflow" form

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.