Variable 'html' referenced before assigned: UnboundLocalError - django

This code previously worked and outputed what I wanted on the website, but then this error happened
from django.shortcuts import render
import json
def get_html_content(fplid):
import requests
API_KEY = "eb9f22abb3158b83c5b1b7f03c325c65"
url = 'https://fantasy.premierleague.com/api/entry/{fplid}/event/30/picks/'
payload = {'api_key': API_KEY, 'url': url}
for _ in range(3):
try:
response = requests.get('http://api.scraperapi.com/', params= payload)
if response.status_code in [200, 404]:
break
except requests.exceptions.ConnectionError:
response = ''
#userdata = json.loads(response.text)
return response.text
def home(request):
if 'fplid' in request.GET:
fplid = request.GET.get('fplid')
html = get_html_content(fplid)
return render(request, 'scrape/home.html', {'fpldata': html})
here is my views.py file. I think I assigned html before, but I'm not sure, how is it referenced before it renders. I added scraperapi for many ip addresses, as I thought maybe I was banned from the api. I am unsure what is going on.
<body>
<h1>Enter Your FPL id </h1>
<form method="GET">
<label for="fplid"> </label>
<input type="text", name="fplid", id="fplid"> <br>
<input type="submit" value="Submit" />
</form>
<h3> {{fpldata}}</h3>
</body>
This is a part of the home.html file if it is relevant

When you initially load the page there probably wont'be an initialized ?fplid=xx. When this isn't present the variable is not assigned a value.
You could initialize the variable with html = None or this:
def home(request):
if 'fplid' in request.GET: # <- when this isnt true
fplid = request.GET.get('fplid')
html = get_html_content(fplid)
return render(request, 'scrape/home.html', {'fpldata': html})
return render(request, 'scrape/home.html')

Related

Unittest Flask-App CSRF-token missing after Post-Request

When unittesting my flask-app and sending post-requests with a Testclient I find a form.error that the CSRF token is missing.
I have been to this and to this post, also read the documentation, however I still cant get my Problem solved.
How the CSRF-Protection is created:
class Routes:
app = Flask(__name__)
app.config['SECRET_KEY'] = WebserverConfig().secret_key
CSRFProtect().init_app(__app)
The view to be tested:
#__app.route("/settings", methods=["GET", "POST"])
def settings():
form = SettingsForm()
if request.method == "POST":
if form.validate_on_submit():
Routes.__reqhandler.put_settings(form=form)
return redirect(url_for("settings"))
if form.errors != {}:
Routes.__reqhandler.error_put_settings(form=form)
data = Routes.__prepdata.prep_settings()
return render_template("settings.html", form=form, data=data)
The template which contains the form:
<form method="POST">
{{ form.hidden_tag() }}
<div class="text-center">
<h3>Einstellungen</h3>
</div>
{{ form.intervall.label }}
{{ form.intervall(id="intervall", class="form-control", placeholder=data["intervall"])}}
</form>
The fixture creating the testclient:
#pytest.fixture
def client():
app = Routes().get_app()
app.config["WTF_CSRF_METHODS"] = []
with app.test_client() as client:
yield client
The test:
def test_settings_valid_intervall(client):
res = client.post("settings", data={"intervall": "00:01:00"}, follow_redirects=True)
assert b'value="00:01:00' in res.data
When printing the data (print(res.data) an alert (created with a flash if there are errors in a form) with the info that "The CSRF token is missing" is sent. What am I missing to tell form.validate_on_submit to not check if a valid CSRF-token was passed?
I solved it by not only emptying the list of the WTF_CSRF_METHODS but also disabling CSRF within the fixture that creates the client:
app.config["WTF_CSRF_ENABLED"] = False

Django : downloading a file (pdf version or odt version) through radio buttons

I trying to propose to the users of my site to download a document in either pdf or odt version through radio buttons. How can I get and use the value of the radio button chosen by the user to serve the appropriate file. So far, I can only serve one at a time.
My current work:
models.py
class File(models.Model):
name = models.CharField(max_length=200)
pdf_version = models.FileField()
odt_version = models.FileField()
def __str__(self):
'''String name represents class File'''
return self.name
urls.py
path('files_page/', views.files_page, name='files_page'),
path('download_file/<int:file_id>/', views.download_file, name='download_file'),
views.py
def files_page(request):
files = File.objects.all()
context = {'files':files}
return render (request, 'walk/files_page.html', context)
def download_file(request, file_id):
#No post request; do nothing
if request.method != 'POST':
pass
else:
#fetch the file to download
#file = File.objects.get(id=file_id)
response = FileResponse(open('/home/me/Desktop/super/media_cdn/tog.pdf', 'rb'))
response['Content-Disposition'] = 'attachment; filename="tog.pdf"'
return response
template
{%block content%}
{%for file in files %}
<p>{{file.name}}</p>
<p>{{file.pdf_version}}</p>
<p>{{file.csv_version}}</p>
<form action="{%url 'walk:download_file' file.id%}" method="POST">
{%csrf_token%}
<input type="radio" name="format" value="pdf" checked> pdf
<input type="radio" name="format" value="csv"> csv
<button name="submit">download</button>
</form>
{%endfor%}
{%endblock content%}
Let's start with using forms. Yes, you use django forms in django instead re-implementing everything yourself.
Create forms.py:
from django import forms
FILE_FORMAT_CHOICES = [("csv", "Download PDF"), ("csv", "Download CSV")]
class FileFormatForm(forms.Form):
file_format = forms.ChoiceField(choices=FILE_FORMAT_CHOICES, widget=forms.RadioSelect())
Inside of the template used by files_page (just let django render the fields, don't do it yourself):
<form action="{%url 'walk:download_file' file.id%}" method="POST">
{% csrf_token %}
{{ form }}
<input type="submit" value="Download">
</form>
And finally adjust the views.py:
def files_page(request):
...
context = {
'files': files,
'form': FileFormatForm() # empty / without POST
}
...
def download_file(request, file_id):
assert request.method == 'POST', "users should only come here with POST now"
form = FileFormatForm(request.POST) # populate from POST
if form.data['file_format'] == 'pdf':
return "return PDF file response here"
else:
return "return CSV file response here"
Note: you don't use tab in Python. Use 4x whitespaces instead.
Another Note: Class Based Views to further reduce the amount of boilerplate.

redirect from django admin action intermediate page to change form page

I am trying to build an admin action 'download_selected' which will download selected models. When the action is selected, I redirect to an intermediate page so that users can select a download format. When a user selects a download format and clicks on 'download', it downloads the file. But stays on the same intermediate page. How do I redirect it back to change form admin page? This redirection that I want is similar to django 'download selected file' default admin action. Thanks.
Here is my code.
admin.py
class SelectDownloadFormatForm(forms.Form):
DOWNLOAD_TYPE_CHOICES=[('csv','csv'),
('json', 'json'),
('xml','xml')]
_selected_action = forms.CharField(widget=forms.MultipleHiddenInput)
download_type = forms.ChoiceField(label=_('Select a Download type'), choices=DOWNLOAD_TYPE_CHOICES, widget=forms.RadioSelect())
def download_selected(self, request, queryset):
import csv
from django.http import HttpResponse, HttpResponseRedirect
import StringIO
form = None
if 'download' in request.POST:
form = self.SelectDownloadFormatForm(request.POST)
if form.is_valid():
dtype = form.cleaned_data['download_type']
print dtype
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="export.csv"'
writer = csv.writer(response)
writer.writerow(['id', 'name', 'qid' ,'label', 'name', 'field'])
count = 0
for s in queryset:
questions_query = ParentModel.objects.filter(parent_form_id = s.id)
for q in questions_query:
writer.writerow([s.id, s.name, q.id, q.label, q.name, q.field])
count += 1
plural = ''
if count != 1:
plural = 's'
self.message_user(request, "Successfully downloaded %d survey response%s in %s format" % (count, plural, dtype))
return response
if not form:
form = self.SelectDownloadFormatForm(initial={'_selected_action': request.POST.getlist(admin.ACTION_CHECKBOX_NAME)})
return render(request,'admin/download_type.html', {'items': queryset,
'download_type_form': form,
})
download_selected.short_description = "Download selected forms"
download_type.html
{% extends "admin/base_site.html" %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{ download_type_form }}
<p>Following survey will be downloaded with corresponding responses:</p>
<ul>{{ items|unordered_list }}</ul>
<input type="hidden" name="action" value="download_selected" />
<input type="submit" name="download" value="Download" />
</form>
{% endblock %}
I added an extra button to go back
Go Back
You'll need javascript for the redirect.
You can use jQuery File Download so you can do:
$.fileDownload('/url/to/download').done(function {
// redirect
})
Not sure if you can combine it with a form post.

Should I use request.GET['...'] or form.cleaned_data.get('...')

I have created a function for a search form form my database, method works fine, but I, don't know whether I should use queryBooks = request.GET['queryBooks'] or form.cleaned_data.get('queryBooks')
Here is my code.
# views.py
def SearchBook(request):
error = False
message = ''
books = Books.objects.all()
if 'queryBooks' in request.GET:
queryBooks = request.GET['queryBooks']
if not queryBooks:
error = True
message = u'enter book or author title'
else:
books = Books.objects.filter\
(
Q(book__icontains=queryBooks) | Q(Author__name__icontains=queryBooks)
)
contexto = {'Books': books, 'Error': error, 'Message': message}
return render(request, 'list_of_book.html', contexto)
# list_of_book.html
<form action="" method="get">
<input type="text" name="queryBooks">
<input type="submit" value="search">
</form>
# urls.py
url(r'^books/search/$', SearchBook, name='searchBook'),
There is no form in your view, so
form.cleaned_data.get('queryBooks')
Would give you an error.
In general, I recommend that you learn about Django forms, as they take care of rendering the html, and validating the input from the user. For your specific example, fetching the query string from request.GET is probably ok.

Duplicate entry in database on refreshing the submitted form in django

After i submit my form using GET method and then refresh the page the data get resubmitted
i am using javascript for form validation
my views are:
def show(request,post_id):
try:
p = post.objects.get(pk=post_id)
c = comment.objects.filter(blog_id=p)
if 'cbox' in request.GET:
c = comment(text=request.GET['cbox'],name=request.GET['cname'],blog=p)
c.save()
c_list = comment.objects.filter(blog_id=p)
except post.DoesNotExist:
raise Http404
return render_to_response('show.html',{'post':p,'c_list':c_list})
my form is:
<form name="comment" action="" method="get" onsubmit="return validateForm()">
<input id="carea" type="text" placeholder="leave a comment" name="cbox" >
<input id="cb" type="submit" value="Post" />
<input id="cn" type="text" placeholder="Name" name="cname">
</form>
i want that when i refresh my page my data should not get resubmited
thanks
If you really insist on using GET to submit which practically is not a good method. You should do a request redirect using HttpResponseRedirect from the server side which will remove the query string from the url . This way it wouldn't resumit the form.
def show(request,post_id):
try:
p = post.objects.get(pk=post_id)
c = comment.objects.filter(blog_id=p)
if 'cbox' in request.GET:
c = comment(text=request.GET['cbox'],name=request.GET['cname'],blog=p)
c.save()
#Do a redirect here
return HttpResponseRedirect("URL of the page you would like to redirect")
c_list = comment.objects.filter(blog_id=p)
except post.DoesNotExist:
raise Http404
return render_to_response('show.html',{'post':p,'c_list':c_list})