Django models to HTML. Then, HTML to PDF - django

I am trying to export django models data (admin side data) into a PDF file.
For that first I created a HTML file to render the data from models.
The HTML file I created
It worked successfully and showed the data from the models correctly.
Successfully worked (I create a url for it to check whether it is working or not)
Then I tried to render the same html file to PDF. I ran the server and I generated a pdf file.
PDF file I expected it will show the data also. But It only showed the table border.
You can see my folders and names in the 1st photo.
I thing it is enough to add this code. If you need full code please tell me.
This is my views.py from app.
def render_to_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
result = BytesIO()
pdf = pisa.pisaDocument(BytesIO(html.encode("ISO-8859-1")), result)
if not pdf.err:
return HttpResponse(result.getvalue(), content_type='application/pdf')
return None
class ViewPDF(View):
def get(self, request, *args, **kwargs):
pdf = render_to_pdf('app/pdf_template.html')
return HttpResponse(pdf, content_type='application/pdf')
Can't I use the same html file to get the data as pdf?
Can anyone tell me what wrong I did?
pdf_template.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>This is my first pdf</title>
</head>
<body>
<center>
<h2>User Table</h2>
<table border="1">
<tr>
<th>Username</th>
<th>E-mail</th>
<th>Country</th>
<th>City</th>
</tr>
{% for result in user %}
<tr>
<td>
{{result.username}}
</td>
<td>
{{result.email}}
</td>
<td>
{{result.country}}
</td>
<td>
{{result.city}}
</td>
</tr>
{% endfor %}
</table>
</center>
</body>
</html>

I got the correct out put. I am posting this as an answer so that it will use for someone else too.
class ViewPDF(View):
def get(self, request, *args, **kwargs):
context={}
context['user'] =user.objects.all()
pdf = render_to_pdf('app/pdf_template.html',context_dict=context)
return HttpResponse(pdf, content_type='application/pdf')
In the views.py I modified the code like above.

i see you used 'user' as a list for your 'for' condition but you never add it to your context. i think it's working fine but there is no data to show
update:
in the 'render_to_pdf' you get 'context_dict' argument to render your template by that. but you never pass this argument when you call your function. that's why you can' see anything except borders. because there is no data
update 2: in this line :
pdf = render_to_pdf('app/pdf_template.html')
just add context dict. something like this:
pdf = render_to_pdf('app/pdf_template.html',context)

Related

Pass variable to view Django

Im pretty new to django. I have created an website that outputs data from an API to an table in django.
I created an app called 'display_networks'. Inside the views.py of this app i have the following code:
from django.shortcuts import render
from django.views.generic import TemplateView
from .services import get_networks
# Create your views here.
class GetNetworks(TemplateView):
template_name = 'networks.html'
#context == dict
def get_context_data(self, *args, **kwargs):
context = {
'networks' : get_networks(),
}
return context
As you can see i import an function called 'get_networks' from .services (services.py).
Inside services.py i have the following code:
import os
import requests
import json
# Headers
apikey = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
headers = {'x-cisco-meraki-api-key': format(str(apikey)), 'Content-Type': 'application/json'}
# Headers
def get_networks():
url = 'https://api.meraki.com/api/v0/organizations/XXXXX/networks/'
r = requests.get(url,headers=headers)
networks = r.json()
return networks
Inside my app i have created a templates folder with an index.html. Inside this index.html i have the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Meraki Networks</title>
<link crossorigin="anonymous"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css"
integrity="sha256-8B1OaG0zT7uYA572S2xOxWACq9NXYPQ+U5kHPV1bJN4="
rel="stylesheet"/>
<link rel="shortcut icon" type="image/png"/>
</head>
<body>
<nav aria-label="main navigation" class="navbar is-light" role="navigation">
<div class="navbar-brand">
<div class="navbar-item">
<img alt="Meraki" src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Meraki_Logo_2016.svg/1920px-Meraki_Logo_2016.svg.png"
style="margin-right: 0.5em;" width="142" height="142">Networks
</div>
</div>
</nav>
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
<thead>
<tr>
<th>Network Name</th>
<th>Network ID</th>
<th>Time zone</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
{% for network in networks %}
<tr>
<td>{{ network.name }}</td>
<td>{{ network.id }}</td>
<td>{{ network.timeZone }}</td>
<td>{{ network.tags }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
This works good and i have the data i want to display.
What i want to do is add an extra column to my table with the header "Get device info" and with an button titled 'Get devices' as the table data.
What this button has to do is send the variable 'network.id' to an different function in my services.py.
I have created a new function inside my services.py with the following code:
def get_devices(net_id):
url = 'https://api.meraki.com/api/v0/networks/'+net_id+'/devices/'
r = requests.get(url,headers=headers)
devices = r.json()
return devices
The variable "net_id" has to come from the button click and the button has to call this function and display the output the same as i have done for the networks.
Does anybody have an idea how i can achieve this?
Thanks in advance!
As the question asks how to pass a variable to a view. The answer is:
Pass it inside the url.
Pass it as a GET or POST parameter in the request (can be done using forms etc.).
Pass it inside the request body.
To solve your problem we shall use the 1st option for it's simplicity. First in your urlpatterns we shall make a pattern that will capture some arguments and these shall be passed to the view:
urlpatterns = [
# Other patterns
path('device/<str:network_id>/', views.GetDevices.as_view(), name='get_devices'),
# Other patterns
]
The <str:network_id> here means that network_id would be passed as a keyword argument to the view. Check more details about the URL dispatcher.
Your view GetDevices would handle getting the devices:
from .services import get_devices
class GetDevices(TemplateView):
template_name = 'devices.html'
#context == dict
def get_context_data(self, *args, **kwargs):
context = {
'devices' : get_devices(kwargs['network_id']),
}
return context
Now in your template with the table for networks you would write the following to add an anchor tag with a link to this new page:
<td>Get Devices</td>
You can style this anchor to look like a button using css / actually put a button tag inside it.
I am sure you know how to make templates so I would leave the task of making 'devices.html' to you.

How to merge the header with main page in Django template where both are getting data from different querysets?

I have a situation where I want to create search in my page. I want to create search without it mixing with the original page as I need to use this same search in more than one page. I created search table in a div in templates folder and named it MySearch.html. Now, I have included that in the main page as {% include 'MySearch.html'%} and it is able to give me the drop down with static text but not with the options that I am filling with query set.
In urls.py -
url(r'Search', myproj.type4.views.ShowSearch, name='Search'),
In ShowSearch() -
def ShowSearch(request):
countryqueryset = Type4Main.objects.all().values('country').distinct()
return render(request,'MySearch.html',{
'countryqueryset':countryqueryset,
})
In MySearch.html -
<!DOCTYPE html>
<html lang="en">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<head>
<meta charset="UTF-8"/>
<title> My Search
</title>
</head>
<body>
<div id ="mysearch" name="mysearch">
<table id="mysearchtbl" name="mysearchtbl">
<tr>
<th>
Country
</th>
</tr>
<tr>
<td>
<select id="country">
<option value="0">Select</option>
{% for country in countryqueryset %}
<option value="{{country.country}}">{{country.country}}</option>
{% endfor %}
</select>
</td>
</tr>
</table>
</div>
</body>
</html>
I can only see Select as option when it is merging with the main page. What am I doing wrong?
Views render templates, not the other way round: templates don't call views. If you are not viewing the page via the ShowSearch URL then the data from that view won't be passed to the template.
For data that needs to be included on every page regardless of the view, use a custom template tag.

Intermediate Django 2.0 admin action page not displaying

I have created an update custom admin action and want to ask for update confirmation by going to a new page before the update action is finalised.
Below is the code for various files:
admin.py`
class ProfileHolderAdmin(admin.ModelAdmin):
list_display = [field.attname for field in ProfileHolder._meta.fields]
actions = ['update_verified']
def update_verified(self, request, querySet):
users_verified=querySet.update(verified='y')
views.update_confirmation(request)
self.message_user(request,"No. of users verified = %s" %str(users_verified))
update_verified.short_description = "Mark selected users as verified"
admin.site.register(ProfileHolder, ProfileHolderAdmin)`
Code in views.py:
def update_confirmation(request):
return render(request,'Profile/confirm_update.html', context=None)
Code in confirm_update.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Confirm Update</title>
</head>
<body>
<form action="admin.py" method="post">
<p>
Are you sure you want to update the verification status of selected users?
</p>
<input type="hidden" name="action" value="update_status" />
<input type="submit" name="apply" value="Update status"/>
</form>
</body>
</html>
The path to the html file is:
website_name\Profile\templates\Profile\confirm_update.html
Profile is the name of my app and website_name is the directory name.
When I carry out the update function, the status is being correctly updated but the intermediate update page is not showing.
Please help.
Your admin action currently returns None which means the admin will take care of redirecting back to the normal change list page.
In order to render the page you need to return the response you get from update_confirmation. Of course you first want to render the page and then run the queryset update. For that you need to find a way to tell the two calls apart (e.g. by adding a POST parameter confirmed).
def update_verified(self, request, querySet):
if request.POST.get('confirmed') is None:
return views.update_confirmation(request)
users_verified=querySet.update(verified='y')
self.message_user(request,"No. of users verified = %s" %str(users_verified))

using Flask Mail to send the rendered form

I am using flask_mail to send emails, while this works for text message, I need some help if i need to send a template itself (what does this mean: so I am rendering a report that contains multiple tables and a text area on the top with a submit button, once the user fills the text area and click on submit, I need flask to send the report containing table along with text data ).
This code fetches data from service now incident table
def incident():
service_now_url = SERVICE_NOW_URL
request_str = req
user = 'username'
pwd = 'password'
url = service_now_url + request_str
headers = {"Accept": "application/json"}
response = requests.get(url, auth=(user, pwd), headers=headers)
json_str = response.json()
records = json_str['result']
return records
This code below is used for rendering the text field and the table.
#app.route('/submit', methods=['GET','POST'])
def submit():
rec = incident()
form = Description(request.form)
shift = form.Shift.data
return render_template('index.html', rec=rec, state=STATES, form=form)
So for sending the email so far, I have written the function below, this sends an email but with table header only and no data.
def send_email(shift):
msg = Message(subject=shift ,recipients=['user#gmail.com'])
msg.html=render_template('index.html')
mail.send(msg)
I am not a Flask expert and still, in the learning phase, any help would be greatly appreciated.
#George thanks for your help, but this is not working, I have pasted below the html template with the modification suggested by you.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> {{ title }} : Graph</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style/main.css') }}">
{% if sending_mail %}
<style>
{{ get_resource_as_string('static\style\main.css') }}
</style>
{% endif %}
</head>
<body>
<form action="/submit">
<p> {{ shiftsum }}</p>
<div align='left'>
<h1>Task</h1>
<table id="customers">
<th>Task Number</th>
<th>Description</th>
<th>State</th>
{% for records in rec %}
<tr>
<td>{{records['number'] }}</td>
<td>{{records['short_description']}}</td>
<td>{{ state[records['state']]}}</td>
</tr>
{% endfor %}
</table>
</div>
</form>
</body>
</html>
Change the definition of send_email() to as given below,
def send_email(shift, rec, STATES):
msg = Message(subject=shift ,recipients=['user#gmail.com'])
msg.html=render_template('index.html', rec=rec, state=STATES)
mail.send(msg)
And in the index.html make sure you enclose the form inside {% if form %} ... {% endif %} where ... indicates your form code.
I hope this helps.
Update (for fixing the missing css styles)
Add the following in your flask script below app = Flask(__name__) or in respective blueprint file,
def get_resource_as_string(name, charset='utf-8'):
with app.open_resource(name) as f:
return f.read().decode(charset)
app.jinja_env.globals['get_resource_as_string'] = get_resource_as_string
Add the following in the index.html
{% if sending_mail %}
<style>
{{ get_resource_as_string('static/css/styles.css') }}
</style>
{% endif %}
Where static/css/styles.css should be replaced with path to your css file. If more than one css file is there, just add {{ get_resource_as_string('static/css/styles.css') }} for each one of them, with their respective path as argument of get_resource_as_string()
Make the following changes in send_email(),
def send_email(shift, rec, STATES):
msg = Message(subject=shift ,recipients=['user#gmail.com'])
msg.html=render_template('index.html', rec=rec, state=STATES, sending_mail=True)
mail.send(msg)
I have added sending_mail=True as argument to the render_template() so whenever sending_mail is set, the render_template will add the content from the css files to the <style>...</style>.
I hope this fixes the missing css styles.
#George thanks for your help, anyways I have found out what was the missing link here,
so the thing is most of the email client doesn't support CSS that are stored locally(this what I found out, could be other things as well), so I used inline CSS and that is working perfectly and now I can see all the formatting that has been done inline in the HTML Template while sending the email.
thanks again for your help.

Flask: Work around a timeout error while running long data collection process

I have a Flask app that will run on OpenShift, that takes a while to generate data and can lead to a timeout error.
From looking at examples, I thought that I could render a "please wait" template which returns immediately, while also calling my long running, run_analysis() function. When the data was finished being collected, run_analysis() would render a new page.
Either this isn't possible or I'm doing it wrong. Thanks for your help.
from flask import Flask
import jinja2
app = Flask(__name__)
please_wait_template = jinja2.Template('''
<!DOCTYPE html>
<html lang="en">
<head>
<title>please wait for data</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Collecting data, this could take a while.</h1>
</body>
</html>''')
input_template = jinja2.Template('''
<!DOCTYPE html>
<html lang="en">
<head>
<title>Input Keywords</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Doing stuff</h1>
<form action="/please_wait/" method="POST">
Enter keywords<br>
<input type="text" name="kw" placeholder="data science"><br>
<input type="submit" value="Submit" name="submit">
</form>
</body>
</html>''')
output_template = jinja2.Template("""
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>results</title>
<meta charset="UTF-8">
</head>
<body>
<h1>RESULTS</h1>
{{ results }}
</body>
</html>
""")
#app.route('/')
def render_input_page():
return input_template.render()
#app.route('/please_wait/')
def please_wait():
return please_wait_template.render()
#app.route('/please_wait/', methods=['post'])
def run_analysis():
kws = request.form['kw']
zips = request.form['zipcodes']
template = run_long_analysis(kws, zips)
return template
def run_long_analysis(kws, zips):
import time
time.sleep(2400)
return output_template(results="testing")
Yes, it is possible. You will want to break out of the single file however. i.e. creating a templates directory and storing your templates there. http://flask.pocoo.org/docs/0.10/quickstart/#rendering-templates
Quoting the docs basic example:
from flask import render_template
#app.route('/hello/')
#app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
You can see render_template is used instead of the .render() or output_template. This is convenient and it makes reading the Flask logic easier.
Specifically to your code:
#app.route('/please_wait/', methods=['post'])
def run_analysis():
kws = request.form['kw']
zips = request.form['zipcodes']
template = run_long_analysis(kws, zips)
The line zips = request.form['zipcodes'] would be troublesome, because I don't see a form under the please_wait route. Perhaps you realize that though.
In general, a tool that will help in situations like this, in my experience is the Flask session object. Which is basically a global dictionary that persists across a user session. See more here in the docs http://flask.pocoo.org/docs/0.10/quickstart/#sessions.
Quoting that example:
from flask import Flask, session, redirect, url_for, escape, request
app = Flask(__name__)
#app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(session['username'])
return 'You are not logged in'
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form action="" method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
#app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
# set the secret key. keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
You can see in the above example that the session object behaves like a dict. It also allows you to dynamically change content displayed, via Jinja2. In other words, it can be passed into the template. These methods in conjunction, should give you the functionality you are looking for.