Crazy Django Error - Variable referenced before assignment - python

Trying to create a django application but getting an UnboundLocalError at /search/ local variable 'results' referenced before assignment error. I can't see the problem as in my code results is assigned - have a look:
def post_search(request):
form = SearchForm()
if 'query' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
cd = form.cleaned_data
results = SearchQuerySet().models(Post).filter(content=cd['query']).load_all()
# count total results
total_results = results.count()
return render(request, 'blog/post/search.html', {'form': form,
'cd': cd,
'results': results,
'total_results': total_results})

Maybe you should initialize results.
Put results = [] before if form.is_valid()

It depends on what you want to do if there are no results. Do you still want to load the view? Then initialise the results variable above any if condition (in this case, the outer one):
...
results = [] #Or whatever empty datatype you need
if 'query' in request.GET:
...
If you don't want to load the view if there are no results, you could move the return render(...) inside the inner if when you're sure there is a results variable to begin with. Then, you can add a fallback return render(...) at the end of the function, outside of any if conditions.
If you always want to load the same view, however, I'd just go for the first one. Adding multiple return render(...) seems more suitable when you want to load a different view when no results were found.

What if form is invalid? For example, user provided incorrect value on no value at all? In this case results is uninitialized. You may:
Initialize it with some empty value like [].
Raise error and return to user info that form is invalid.

def post_search(request):
cd = ""
total_results = ""
form = SearchForm()
if 'query' in request.GET:
form = SearchForm(request.GET)
if form.is_valid():
cd = form.cleaned_data
results = SearchQuerySet().models(Post).filter(content=cd['query']).load_all()
# count total results
total_results = results.count()
return render(request, 'blog/post/search.html', {'form': form,
'cd': cd,
'results': results,
'total_results': total_results})
now you have to write validation code in template

If you want to use the definition in a recursive way(count the results in your case) you should assign the var results outside the definition, because if you do it will reset every time you call it. You have to assign it out of the definition and state it like a global var.
global results
results = ''
I personally recommend you to do a bit more reading and learning on python before coding with Django.
Good luck ! :)

Related

This code is returning the error 'local variable 'name' referenced before assignment'.I am trying to keep a form and formset together

I am trying to make a form and formset work together which are not related to each other.Any suggestions if there are easy solutions to this?
def resume(request):
form=ResumeForm(request.POST)
ExperienceFormSet=formset_factory(Experience)
formset=ExperienceFormSet(request.POST,request.FILES)
print(form.is_valid)
if request.method=='POST':
if form.is_valid() and formset.is_valid():
name=request.POST.get('name')
email=request.POST.get('email')
phone=form.cleaned_data['phone']
objective=request.POST.get('objective')
branch=request.POST.get('branch')
course=request.POST.get('course')
course_total=course+' in '+branch
department=request.POST.get('department')
other_link=request.POST.get('other_link')
for f in formset:
cd=f.cleaned_data
companyName=cd.get('companyName')
print(companyName)
else:
form=ResumeForm()
ExperienceFormSet=formset_factory(Experience)
formset=ExperienceFormSet()
return render(request,'resume.html',{'name':name,'email':email,'phone':phone,'objective':objective,'course_total':course_total,'department':department,'other_link':other_link})
if request.method isn't 'POST', the variable name will never be created. Thus, when your function tries to return this part: {'name':name it won't find the variable name and it will fail.
These two lines might cause the problem:
if request.method=='POST':
if form.is_valid() and formset.is_valid():
First if request.method is not a POST then the name variable will not be created thus name referenced before assignment error will occur. Same for second line of code too. You can solve this problem with like this:
if request_method=='POST':
form_valid=form.is_valid()
formset_valid=formset.is_valid()
if form_valid and formset_valid:
# ...
# ...
if request_method=="POST" and form_valid and formset_valid:
return render(request,'resume.html',{'name':name,'email':email,'phone':phone,'objective':objective,'course_total':course_total,'department':department,'other_link':other_link})
else:
# Something is not valid you need to handle this.
There are quite a few things wrong with your view: you create a bound form right from the start (uselessly when it's a GET request), you use the raw request.POST data instead of the sanitized ones from the form's cleaned_data, and - the issue you mention - you try to inconditionnally use variables that are only conditionnally defined.
The proper canonical function-based-view template for form submissions is:
def myview(request):
# if you have variables that you want to always
# be in the context, the safest way is to define
# them right from the start (with a dummy value):
name = None
# now FIRST test the request method:
if request.method == "POST":
# and ONLY then build a bound form:
form = MyForm(request.POST)
if form.is_valid():
# use sanitized data, not raw POST data:
name = form.cleaned_data["name"]
else:
# build an unbound form
form = MyForm()
# here you know that both variables have been defined whatever
return render(request, "mytemplate.html", {"form": form, "name": name}

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'], {})

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 sessions keeps returning 'NoneType' error

I am currently using Django forms to have users enter information, which I then use Django sessions to call in other view functions in my views.py file. Currently my form is processed in the view function, 'search' and is called using sessions in latter view functions. However, when I enter data into my form and submit it, I get the error:
cannot concatenate 'str' and 'NoneType' objects
Here is my code, thus far:
def search(request):
t = request.session.get("tick")
if request.method == 'POST':
search = Search(data=request.POST)
if search.is_valid():
success = True
ticker = search.cleaned_data['search']
request.session["tick"] = ticker
else:
print search.errors
else:
search = Search()
def search_overview(request):
result = {}
context = RequestContext(request)
t = request.session.get("tick")
sourceCode = urllib2.urlopen("http://finance.yahoo.com/q/ks?s="+t).read()
pbr = sourceCode.split('Price/Book (mrq):</td><td class="yfnc_tabledata1">')[1].split('</td>')[0]
result['pbr'] = pbr
return render_to_response('ui/search.html', {"result":result}, context)
Anyone have any ideas on how I can fix this, so that I can use sessions to store data touse in different view functions? Thanks.
In your search_overview function, t is getting set to None. Which cannot be concatenated with a string.
You could try this as a quick fix, but it doesn't solve the issue of tick not being in the session:
t = request.session.get('tick', '')
This returns the value for the tick key in session if it exists, otherwise it returns an empty string. Which can be concatenated to another string.

Categories

Resources