Repeating a large form in a form wizard - django

I'm trying to create a form wizard which starts with a repeatable form for adding multiple trainees.
The user can add as many trainees as they like, with each of the "Add Trainee" forms being quite a long form asking for lots of details.
Once the user has added all of their trainees, there are 3 or 4 more forms before the wizard is complete.
What is the best way to do this?
I was wondering whether adding the trainees to the session would be the simplest idea but this might break some of the wizard functionality, like being able to go back etc.
And because the "Add Trainee" form is so long, it doesn't make sense to use a formset on the first form wizard page, I don't think.
Any ideas?

You might be able to get away with modifying the post method with something like this.
if form.is_valid():
# if the form is valid, store the cleaned data and files.
self.storage.set_step_data(self.steps.current,
self.process_step(form))
self.storage.set_step_files(self.steps.current,
self.process_step_files(form))
# check if the current step is the last step
if self.steps.current == self.steps.last:
# no more steps, render done view
return self.render_done(form, **kwargs)
# check if trainee form is current form
elif self.steps.current == "trainee_form":
mentor = User.objects.get(id=self.request.user.id)
Trainee.objects.create(
mentor=mentor,
data1=self.request.POST["trainee_form-data1"]
data2=self.request.POST["trainee_form-data2"]
data3=self.request.POST["trainee_form-data3"]
)
if self.request.POST["trainee_form-add_new"] == True:
self.render.go_to_step["trainee_form"]
else:
return self.render_next_step(form)
else:
# proceed to the next step
return self.render_next_step(form)
return self.render(form)
You'll have to fiddle with it, but hopefully it sparks an idea.

Related

Is there a more elegant way to write this django view?

I've been messing around with django and I have this django view:
def handle_results(request):
if request.method == "POST" and request.is_ajax():
# Do something with the post request
elif request.method == "GET" and request.is_ajax():
# Do something with the get request
else:
# First time in this view, render first element to display
return render(
request, "result_page.html", context={"display": arr[0]}
)
The main idea is, this is supposed to be a Same Page Application, and the first time I'm in this view, I need to render the contents of the array to display to the user, after that, the user can interact with said array via the html (think of it as upvoting or downvoting stuff that's shown). Depending on the user's choice, I get a GET or POST request and need to deal with said request.
However, the way I'm implementing this seems not that elegant and I was wondering if there'd be another better way to accomplish what I'm doing.
Thank you so much!
I would suggest using a class based view

Dynamically Chain Django Forms

In a Django 1.11 app I've created a large form for users to update a model instance, but based on what the users change there may be multiple other forms I'd like to redirect them to afterwards.
What's the best practise for dynamically chaining multiple forms (dynamic workflow)?
I can't find anything helpful in the Django docs or other questions on here.
E.g. (extremely simplified) view for model update form:
def asset_edit(request, pk):
asset = get_object_or_404(Asset, pk=pk)
current_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if request.method == "POST":
form = EditAsset(request.POST, request.FILES, instance=asset)
if form.is_valid():
asset = form.save(commit=False)
# setting some things...
asset.save()
new_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if current_location_concat != new_location_concat:
check_relatives = True # redirect to check_relatives form
if asset.equipment_id:
if Asset.objects.filter(equipment_id=asset.equipment_id).count() > 1:
duplicate_assets = True # redirect to check_duplicates form
# if check_relatives and duplicate_assets:
# redirect to check_duplicates form and then on to check_relatives form
return redirect("asset_detail", pk=asset.pk)
I know I could just add a new URL for my check_duplicates form with a "next" (or similar) parameter, pass a "next" value that the check_duplicate view maps to one or more other forms (just "check_relatives" in this instance) and redirects to this when the check_duplicates form is submitted, but is this the best practise?
Especially given that the number of forms that may need to be chained this way could get quite large and the logic complex!
I have been using formtool's WizardView for this and I have tried the 'stuff-everything-into-the-post-method' approach as well.
WizardView looks like the right fit for what you are trying to do.
For example, it allows you to skip steps in your workflow based on function conditions - removing this bit of logic from your workflow, which makes the whole view easier to read.
I have found it a pain to write tests for such views as you have to account for the WizardView's very own internal structure while testing, but overall it's definitely better than writing a 1000 lines post-method (might as well code in BASIC then).

Django practice: smaller views or less views?

I'm developing an app with django, and I have a view where I use 2 return render_to_response, with two different html files, depending on the status of the user.
I was wondering if it would be a better practice to split my view into two different views or if I should keep a bigger view.
What are the pros and the cons of doing so?
Sorry if my question is not clear. Thank you very much for your advices.
There's no right or wrong answer to this question so your question may not be acceptable on stackoverflow, which usually is intended for questions/problems with specific technical solutions.
That said, here's my view on this topic - I personally like to keep my view function small and if further processing is required, split them out into smaller functions.
For example:-
#permission_required('organizations.organization_add_user')
def organization_add_user(request, org_slug):
org = get_object_or_404(Organization, slug=org_slug)
form = OrganizationAddUserForm(org=org)
if request.method == 'POST':
form = OrganizationAddUserForm(request.POST or None, request.FILES or None, org=org)
if form.is_valid():
cd = form.cleaned_data
# Create the user object & send out email for activation
user = create_user_from_manual(request, data=cd)
# Add user to OrganizationUser
org_user, created = OrganizationUser.objects.get_or_create(user=user,\
organization=org)
dept = org.departments.get(name=cd['department'])
org_user.departments.add(dept)
# Add user to the appropriate roles (OrganizationGroup) and groups (django groups)
org_groups = OrganizationGroup.objects.filter(group__name__in=cd['roles'], \
organization=org)
for g in org_groups:
user.groups.add(g.group)
return HttpResponse(reverse('add_user_success'))
template = 'organizations/add_user.html'
template_vars = {'form': form}
# override request with dictionary template_vars
template_vars = FormMediaRequestContext(request=request, dict=template_vars)
return render(request, template, template_vars)
FormMediaEquestContext is a class I import from another file and has its own logic which helps me to handle javascript and css files associated with my form (OrganizationAddUserForm).
create_user_from_manual is yet another function which is encapsulated separately and deals with the reasonably convolutated logic relating to creating a new user in my system and sending an invitation email to that new user.
And of course, I serve up a different template if this is the first time a user arrives on this "add user" page as opposed to redirecting to a completely different url with its own view function and template when the add user form is successfully executed.
By keeping our view functions reasonably small, I have an easier time tracking down bugs relating to specific functionality.
In addition, it is also a good way to "reuse" my utility functions such as the create_user_from_manual method should I need this same utility function in another view function.
However, at the end of the day, organizing code and encapsulating code is a judgement call that you get to make as you progress as a developer.

Getting checkbox value in controller

I have a template that creates a text entry field and a checkbox. When the checkbox is unchecked, the text field is disabled and cleared, when it's checked, it's enabled, and the user may or may not have typed in it. In my controller I need to distinguish between the 2
cases when the checkbox is unchecked, and the checkbox is checked but the text field is blank. I can get the value of the text field, but not of the checkbox. Is there some way to do this? I've googled this, and I see it's been asked a few times here, but noneof the solutions seem to work for me.
request.POST.get('my_checkbox_field')
P.S. In Django, they're called "views" not controllers.
UPDATE (based on comment)
I'm taking "controller" to mean "view" since Django doesn't have a concept of controllers and they're closest to views. If that's not the case, by all means correct me. Given that, all function-based views at the very least require a request parameter. If you're using class-based views, then request is simply stored on the view object, so you just need to modify it to self.request. I suggest you take some more time to thoroughly read the docs, as this is pretty much bare minimal understanding stuff that is well documented.
Are you looking for this?
def myview(request):
form = MyForm()
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
checkbox = request.POST.get('my_checkbox', False) # will be True if checked
if checkbox:
# check textfield content
else:
# do something else
return render_to_response(template, kwvars, context_instance=RequestContext(request))

How to write good form validation in Django?

I've seen Django's samples and I can see they have decent error handling. However I want to see if there is yet a better approach, a general pattern to handle form validation errors in Django. This is the sample I found here:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
In particular, I was wondering:
How can the view in "/thanks/" be sure that the form was validated? Are there any common ways to pass the successful validation of the form to the next view? Or do I need to do something manually such as setting a flag in request's session?
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
EDIT: With regards to #1 I am referring to cases like user manually entering the '/thanks/' url or going back and forth through history pages and accidentally openning it without any form being validated. (Do we still show the "thanks" page? or we need to somehow re-validate why we are in thanks view).
The view can be sure that the form is validated because it will only be called if the form is valid...
If the page is generated through a post request the browser will always ask you that when hitting refresh... I guess the only way to avoid this would be redirecting to another page!
How can the view in "/thanks/" be sure that the form was validated?
form.is_valid() should thoroughly check any field or - if necessary - any combination, cornercase, etc. That's basically it. The views knows, the form was valid if it renders. There is no need to include redundant information in the session.
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
I am not sure what the point would be. The form contains errors and the user may correct them or leave. To render a page that would not ask for form resubmission, one could use a redirect, just as in the valid case. The error markup would have to be done manually in that case.