Django convert current page to PDF on button click - django

I am using Django 1.10 and python 3.4 on windows. I set up pdfkit and it works perfectly from command line. I want to use it in the site on a button to return the current page as a pdf attachment without redirecting anywhere(if possible). The page to convert is a report which uses a user specified date as url.
Currently the button redirects by appending the current url with "?mybtn=Generate+PDF#"
I have seen some solutions using jQuery or javascript, but I only really know python well. I will learn those eventually, but for now I would like a quicker solution where I understand what's going on.
views.py
class FlashReport(generic.View):
template_name = "reports_flash_report.html"
def get(self, request, year, month, day):
report_data = get_flash_report_data(int(year),int(month),int(day))
return render(request, 'reports_flash_report.html', report_data)
def generate_PDF(request):
path = request.get_full_path()
pdf = pdfkit.from_url(path, False)
response = HttpResponse(pdf, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="flash_report.pdf"'
return response
reports_flash_report.html
<div>
<form action="" method="get">
<input type="submit" class="btn" value="Generate PDF" name="mybtn" />
</form>
</div>
urls.py
url(r'^flash_report/(?P<year>[0-9]{4})-(?P<month>[0-9]{2})-(?P<day>[0-9]{2})/$', views.FlashReport.as_view(), name="flash_report"),

Related

Redirect user to previous page after submitting form in Django

I am trying to redirect the user to the previous page once a form is submitted.
User starts on Venue_id page (http..../show_venue/23/) and clicks on product id
User is directed to Product_id page (http..../show_product.../1/) and find a form
User completes form and submit on Product_id page
user redirect to Venue_id page upon form submission (from http..../show_venue/23/)
http..../show_venue/23/ -> http..../show_product.../1/ -> http..../show_venue/23/
I found a good source of inspiration on the forum, particularly on this page (How to redirect to previous page in Django after POST request)
(I also found some posts using the history of the browser. But decided to stick to this method, as it seems using browser history doesn't always work)
I used next as suggested in the code in the above post. But there is something I don't understand which is probably why I get this wrong.
Here is the different codes in views I tried for next:
next = request.POST.get('next','/') => this sends me to '/', which
is not what i want. However it seemed to work for the person who
posted the original question even though they were trying to NOT be redirect to '/';
next = request.POST.get('next','') => sends me to my product_id page url, but the page is empty
next = request.POST.get('next') => this one was suggested in other posts, but I get Field 'id' expected a number but got 'None'.
I might be completely wrong, but I feel the key is probably there. How to do refer to "show_venue/<venue_id>" into "next = request.POST.get('next','show_venue/<venue_id>')"?
In terms of code
Views
def show_product_from_venue(request, product_id):
product = Product.objects.get(pk=product_id)
form = ReviewForm()
venue_form = VenueForm()
submitted = False
next = request.POST.get('next')
if request.method == "POST" and 'btnvenue_form' in request.POST:
venue_form = VenueForm(request.POST)
if venue_form.is_valid():
venue_form.save()
return HttpResponseRedirect(next)
else:
venue_form = VenueForm
if 'submitted' in request.GET:
submitted = True
else:
print(form.errors)
return render(request,"main/show_product_from_venue.html", {'form':form, 'submitted':submitted, 'product':product, 'venue_form':venue_form, 'data':data})
Venue_id (template)
<a href="{% url 'show-product-from-venue' product.id %}?next={{ request.path|urlencode }} method="POST">
Product_id(template with form)
<form action="{% url 'show-product-from-venue' product.id%}" method="POST">
{% csrf_token %}
{{ form}}
<input type="submit" name="btnreview_form" name="next" value="{{ request.GET.next }}" class="btn btn-primary custom-btn">
</form>
urls
#VENUE PAGE
path('show_venue/<venue_id>', views.show_venue,name="show-venue"),
#PRODUCT
path('show_product_from_venue/<product_id>', views.show_product_from_venue,name="show-product-from-venue"),
If you want to go previous pages then just add the root where exactly you want to go:
In views:
EX:
return HttpResponseRedirect("/users/")

Testing Django view

I'm trying to test the following view
def generate_exercise_edl(request, ex_pk, unit_pk, *args, **kwargs):
ex_instance = Exercises.objects.get(id=ex_pk)
unit_instance = Units.objects.get(id=unit_pk)
unit_edl = UnitEdl.objects.filter(unit=unit_instance)
locations = Locations.objects.all()
unit_edl = list(unit_edl)
print(request)
print(request.POST)
print(request.user)
if request.method == "POST":
for item in unit_edl:
ExerciseEdl.objects.update_or_create(unit=unit_instance, exercise=ex_instance, equipment=item.equipment,
quantity=item.quantity, location=Locations.objects.get(location="Okinawa"))
print(request)
return redirect('exercise-equipment', ex_pk=ex_pk, unit_pk=unit_pk)
else:
messages.error(
request, f'Failed to add/update the {unit_instance.unit_name} edl for {ex_instance.exercise}.')
context = {
'ex_instance': ex_instance,
'unit_instance': unit_instance,
'unit_edl': unit_edl,
'locations': locations,
}
return render(request, 'exercise/exercise_edl.html', context)
This is my test code
def test_generate_edl(self):
unit_edl = UnitEdl.objects.filter(unit=unit.pk)
for edl in unit_edl:
ExerciseEdl.objects.update_or_create(
unit=unit,
exercise=ex,
equipment=edl.equipment,
quantity=edl.quantity,
location=loc
)
response = self.client.post(
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/')
ex_edl = ExerciseEdl.objects.all().count()
self.assertEquals(ex_edl, 2)
self.assertEqual(response.status_code, 302)
This is the URL for the view
path('exercise/<int:ex_pk>/edl/<int:unit_pk>/convert', views.generate_exercise_edl, name='generate-edl'),
And the part of the template that calls my function
<form action="{% url 'generate-edl' ex_pk=ex_instance.id unit_pk=unit_instance.id %}" method="post">
{% csrf_token %}
<input class="btn btn-primary btn-sm mt-2" type="submit" value="Generate EDL">
</form>
My test returns 404, not 302, but the function on the site works, and redirects you.
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/' isn't mapped to any template, it's just the url for the function. In the past my tests have returned a status code of 404 when I wrote the post data incorrectly.
print(request.POST) returns:
<QueryDict: {'csrfmiddlewaretoken':
['ZYT0dgMZqqgmCo2OufdI9B0hIJ5k5qPKcxnkReWPZy0iY9McaBO7MHENjYLzH66O']}>
Which makes sense because I'm not sending any post data, just the csrf token.
What I want to know is, am I on the right track with using 'response = self.client.post(
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/')'?
With my other tests I include the post data in a dictionary along with the URL, but this function doesn't use any, so I just ran a similar function.
Is there a better way to test this? Should I just refactor?
You need to use reverse to build your URL rather than hard coding it. Since you hard coded it, it is getting a 404 since the URL the test tried to post to is incorrect.
I don't know the app_name in your URLs file, you will need to add that to the reverse. For example if it was excercise it would be exercise:generate-edl.
from django.urls import reverse
response = self.client.post(reverse(
'<app_name>:generate-edl',
kwargs={
ex_pk: ex.pk,
unit_pk: unit.pk,
}
))

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.

Scrapy FormRequest How to check if URL is needed

I am new to scrapy and in general web tech.
While working on a scrapy example to perform auto login. I came across 1 field , referrer url . I am wondering when do i need to this.
return scrapy.FormRequest.from_response(
response,
url='www.myreferrer.com', #when do i need this ???
formnumber=1,
formdata=self.data['formdata'],
callback=self.after_login
)
I tested with and without it and it works in both instances.
I understand that referrer url is for security but how do i determine from html code that i need or dont need this ?
ADDON
The following html form required the url to be defined :
<form id="login" enctype="multipart/form-data" method="post" action="https:///myshop.com/login/index.php?route=account/login">
I am a returning customer.<br>
<br>
<b>E-Mail Address:</b><br>
<input type="text" name="email">
<br>
<br>
<b>Password:</b><br>
<input type="password" name="password">
<br>
Forgotten Password<br>
<div style="text-align: right;"><a class="button" onclick="$('#login').submit();"><span>Login</span></a></div>
</form>`
class FormRequest(Request):
# delete some code here
#classmethod
def from_response(cls, response, formname=None, formid=None, formnumber=0, formdata=None,
clickdata=None, dont_click=False, formxpath=None, formcss=None, **kwargs):
url = _get_form_url(form, kwargs.pop('url', None))
def _get_form_url(form, url):
if url is None:
return urljoin(form.base_url, form.action)
return urljoin(form.base_url, url)
if the url is empty, it uses form tag's action attribute to get the URL.
if the url is not empty, then it use the URL you give to it.
the base_url comes from the response.
def _get_form(response, formname, formid, formnumber, formxpath):
"""Find the form element """
root = create_root_node(response.text, lxml.html.HTMLParser,
base_url=get_base_url(response))
so, when the action attribute does not exist or the login requests is not sent to the action URL, you need to pass the argument.

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.