Avoid recreating the GET logic in the POST part of a view - python

How can I avoid duplicating the logic code from the GET block in the view below?
The logic is view-specific enough that I don't feel it makes sense to put a helper function in a separate utils.py.
'''
show_stuff view shows different stuff depending on how many other
POST requests have been submitted to view and saved to the DB.
All the users access the same URL randomly, so I don't believe it's possible to
split things up like "view_for_template1", "view_for_template2" in urls.py
'''
def show_stuff(request, url_dispatch_var1, url_dispatch_var2=None):
if request.method == "GET":
#30 lines of logic determining which Template and Context to return
if request.method =="POST":
#10 lines of logic determining which Form type to save
#then, the same 30 lines of logic as the GET block to determine
#which Template and Context to return

You can usually do something like the following:
def show_stuff(request, url_dispatch_var1, url_dispatch_var2=None):
if request.method =="POST":
#10 lines of logic determining which Form type to save
# redirect if form is valid
else:
# this is a GET request
form = MyForm() # unbound form for get request
# 30 lines of logic to determine which Template and Context to return
return render(request, template, context)
Note that after a successful post request, the usual approach is to redirect to prevent duplicate submissions.
This might be a case where class based views are useful. You could subclass FormView, then override get_context_data, get_template_names and so on.

Maybe instead of returning body for POST request you could redirect user to your GET view ?

Related

How can I use form data in Django directly as context in another view

I'm having big trouble understanding the whole forms business in django. As I understand it the cleaned form data is a dictionary. So all my defined form fields should be in the dictionary like so: {'definedform': userinput, ...}. Is this correct?
I want to create a form in which a user can input data. This data should then be send to a different view, in which the inputted data is rendered with a latex template (and subsequently rendered into a pdf). This works more or less fine if I define the context in the /create_pdf/ view and grab the user input manually. But I suppose there is a nicer way. What I think should work:
def index(request):
if request.method == "POST":
persoform = PersonalForm(request.POST, prefix='personal')
if persoform.is_valid():
content = persoform.cleaned_data()
content = Context(content)
return HttpResponseRedirect('/create_pdf/')
else:
persoform = PersonalForm()
return render(request, 'app/template.html', {'persoform': persoform})
And in my /create_pdf/ view:
def create_pdf(request):
template = get_template('app/latextemplate.tex')
rendered_tpl = template.render(content)
[...]
So, how can I make sure, to pass the data from my index view to my create_pdf view?
EDIT:
Forgot to mention: The error is "'content' not defined". So I understand that the /create_pdf/ view doesn't get content dictionary, but I have no idea how I would make sure that it does.
Put the data in to the session on submit, and pop it out in the second view.
if form.is_valid():
request.session['perso'] = form.cleaned_data
return HttpResponseRedirect('/create_pdf/')
...
def create_pdf(request):
data = request.session.pop('perso'], {})

django - how to implement a 2-step publish mechanism

I'm new to both web development and django so maybe that's a noob question.
I want to do the following:
Ask user to fill some form and submit it.
Then, parse and format the content and display it back to the user to let him verify it.
User can accept the result or go back to the previous view, update data and resend.
This is as far as I can think:
views.py
def add_content(request):
if request.method == 'POST':
form = AddContentForm(request.POST)
if form.is_valid():
content = form.save(commit=False)
return verify_content(request, content)
else:
form = AddContentForm()
return render(request, 'myapp/add_content.html', {'form' : form})
def verify_content(request, content):
return render(request, 'myapp/verify_content.html', {'content' : content})
The verify_content template will obviously contain two buttons ('back', 'ok'), but I don't know how to pass the content object to a view for saving it in the db, or send it back to the previous view from there. Should I use js? Can i do it with just server side code?
Maybe my whole logic is wrong. Should I save the object in the db before verification and then delete it if needed (sounds ugly)? What is a good way to implement this?
Thanks in advance for your time.
You could use the users session for this:
request.session['content'] = content
and in the view where the user should verify his input do:
content = request.session['content']
and voilá you got the content between 2 views.
Django also secures that users can't tinker with its data by either saving it server side, or in a signed cookie.
I would save the form with commit=True in the add_content view, and would add a verified field or something to the model. Then you can append the pk as GET parameter to the link which will get you back to add_content view from verify. You can extract the parameter from request.GET dict.

How to Prevent a Redirected Django Form from Executing Twice?

My form2 is executing twice due to HttpResponseRedirect and from 'POST'. How do I prevent that from happening? Is it even possible?
What I've tried:
Process and render "getInfo" from form 1 and display it in form2. While this may work but I'll still end up going through the "getInfo" again in form2 to be able to use the returned variable.
Putting "getInfo" inside the if request.method will create an error because getInfo will need to be executed to obtain the returned errors variable.
Any suggestion is definitely welcomed.
Update
I've raised a similar question regarding "Why My Django Form Executed Twice?" and it was answered. I didn't want to create a bigger confusion by adding more questions on top of it. I created this as a follow-up question on how to actually solve it.
views.py
def form1 (request):
NameFormSet = formset_factory (NameForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)
if request.method == 'POST':
name_formset = NameFormSet (request.POST, prefix = 'nameform')
if name_formset.is_valid ():
data = name_formset.cleaned_data
request.session ['data'] = data
return HttpResponseRedirect ('form2')
else:
name_formset = NameFormSet (prefix = 'nameform')
context = {'name_formset': name_formset}
return render (request, 'nameform/form1.html', context)
def form2 (request):
data = request.session ['data']
n, data, errors = getInfo (data) # <==== This statement executed twice in the console
CheckBoxFormSet = formset_factory (CheckBox, extra = 2, max_num = 5)
if request.method == 'POST':
checkbox_formset = CheckBoxFormSet (request.POST, prefix = 'checkbox')
if checkbox_formset.is_valid ():
for i, form in enumerate (checkbox_formset.cleaned_data):
data [i].update (form) # Join cleaned data with original data
n.processData (data, errors) # <=== n object will be used from getInfo
del request.session ['data']
context = {'data': data}
return render (request, 'nameform/success.html', context)
else:
checkbox_formset = CheckBoxFormSet (prefix = 'checkbox')
context = {
'checkbox_formset': checkbox_formset,
'data': data,
'errors': errors, # <==== getInfo needed to execute first to display errors messages
}
return render (request, 'nameform/form2.html', context)
def getInfo (data):
# Do something with data
return (n, data, errors)
Should the else: statement be less indented to align with if request.method == POST:? Also, where does getInfo come from? In the version of django I'm using (1.7) errors are an attribute on the formset after calling is_valid().
edit: further information
OK so your getInfo function runs twice because HttpResponseRedirect actually returns a 302 response to the browser with the new address, which the browser then GETs. Then when the user submits the form the browser POSTs the data to the same view. Since getInfo runs at the start of your view before any GET/POST conditional check, it runs both times. If you just want to delegate returning a response to another view function, you can call it directly, don't return a redirect.
Without knowing any more about your program this is as much as anyone can tell you.
Some more points:
getInfo sounds like it should be a 'safe' function that doesn't mutate its input or have any side effects, so running it twice shouldn't be a problem. If it does either of those things, you ought to rename it at least.
If the results of getInfo aren't expected to change between the GET and the POST request then you can move it into the form1 view function and store the results in session['data']. If it is expected to change, or you need to know if it does change, then you have no option but to run it twice anyway, unless there is some conditional you can check without running it to know if it will change.
Finally, form validation shouldn't be in the view if possible, keep it in your form class. There are hooks in django's form classes for whatever kind of validation you could want to do on submitted data. As a general rule, try to work within the framework, in the way it was designed to be used. This approach, as opposed to constructing a Rube-Goldberg machine out of the scavenged parts of many libraries taped together, will save you a lot of effort in the long run, as the library author and you will be working in the same direction.

How do I pass a list from one view to another in Django?

I've been scouring StackOverflow, but I haven't found an answer to this that works for me. I am relatively new to Python and Django, so maybe I'm thinking about it wrong.
To make a simple example, imagine two views with different associated URLs. This is not supposed to be perfect code. I'm just trying to figure out how to get a variable-length list of items from view 1 into view 2. I don't see a way to do it via the URL because the list may be variably long. Shouldn't this be extremely easy to do?
def view2(request, list_to_process):
use list_to_process to manufacture formset (e.g. make a formset with one entry for each item in the list)
return render(request, 'Project/template2.html', {'formset': formset})
def view1(request):
if request.method == "POST":
if form.is_valid():
result = form.cleaned_data
list_to_process = []
for item in result:
list_to_process.append(item)
*WHAT CODE DO I USE HERE TO CALL VIEW2 AND SEND IT list_to_process AS AN ARGUMENT OR REQUEST ADDITION?*
else:
formset = formsettype()
helper = AssayHelper() (defined elsewhere)
helper.add_input(Submit("submit", "Submit")
return render(request, 'Project/template1.html', {'formset': formset, 'helper': helper})
Can someone please help? Thanks.
That is exactly what the session is for. In view 1:
request.session['list'] = list_to_process
And in view 2:
list_to_process = request.session['list']
If you are willing to use session then go with the answer given by #Daniel,
But in your case it seems that you are not going on separate url, you just need to render it in the same url but need the output from that view, in that case take help from named paramter of python functions like this -
def view2(request, list_to_process=None, **kwargs):
use list_to_process to manufacture formset (e.g. make a formset with one entry for each item in the list)
return render(request, 'Project/template2.html', {'formset': formset})
def view1(request):
if request.method == "POST":
if form.is_valid():
result = form.cleaned_data
list_to_process = []
for item in result:
list_to_process.append(item)
return view2(request, list_to_process=list_to_process)
else:
.....
The benefit of using named parameter is that, they are optional and thus will not throw error if they are not provided, for example, when that view is called directly instead from inside view1

django forms - reusing form request functions

I have a base.html which most of my pages inherit. Inside it defines a header which contains a search box. The code for the search box is some thing along the lines of....
if request.method == 'POST':
if 'search_button' in request.POST:
location = request.POST['location']
# Do something with location here and redirect to another page...
return HttpResponseRedirect('/lostandfound')
I have a couple of questions. First of all, how do I ensure that this code is sat in all my other views without rewriting it every time, this wouldn't be very DRY!!
Also how do I redirect to another page and pass the variable 'location' along with it? HttpResponseRedirect isn't the right way to do it i'm sure!
You should POST your search form to it's own search view, and display the results on that page.

Categories

Resources