Python ReportLab - Clickable TOC with X of Y page numbering - python-2.7

Using ReportLab 2.7, Python 2.7:
I can create a pdf with clickable bookmarks in the table of contents (toc), or I can create a pdf with "x of y" page numbering, but the bookmarkpage(key) appears to break when I try to do both.
Example code:
class MyDocTemplate(BaseDocTemplate):
def __init__(self, filename, **kw):
self.allowSplitting = 0
BaseDocTemplate.__init__(self, filename, **kw)
template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm)]) #, id='F1'
self.addPageTemplates(template)
def afterFlowable(self, flowable):
if isinstance(flowable, Paragraph):
txt = flowable.getPlainText()
style = flowable.style.name
if style == 'Heading1':
key = 'h1-%s' % self.seq.nextf('heading1')
self.canv.bookmarkPage(key)
self.notify('TOCEntry', (0, txt, self.page))
elif style == 'Heading2':
key = 'h2-%s' % self.seq.nextf('heading2')
print key
self.canv.bookmarkPage(key)
self.notify('TOCEntry', (1, txt, self.page, key))
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
canvas.Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
"""add page info to each page (page x of y)"""
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_page_number(self, page_count):
self.setFont('Times-Bold',14)
self.drawRightString(7.6*inch,.5*inch,
"Page %d of %d" % (self._pageNumber, page_count))
h1 = PS(name = 'Heading1',
fontSize = 14,
leading = 16)
h2 = PS(name = 'Heading2',
fontSize = 12,
leading = 14,
leftIndent = 25)
#Build story.
story = []
toc = TableOfContents()
#For conciseness, using the same styles for headings and TOC entries
toc.levelStyles = [h1, h2]
story.append(toc)
story.append(PageBreak())
story.append(Paragraph('First heading', h1))
story.append(Paragraph('Text in first heading', PS('body')))
story.append(Paragraph('First sub heading', h2))
story.append(Paragraph('Text in first sub heading', PS('body')))
story.append(PageBreak())
story.append(Paragraph('Second sub heading', h2))
story.append(Paragraph('Text in second sub heading', PS('body')))
story.append(Paragraph('Last heading', h1))
doc = MyDocTemplate("mypdf.pdf")
doc.multiBuild(story, canvasmaker=NumberedCanvas)
Any comments would be greatly appreciated.

found this initially looking for a solution to no avail, however, figured something out which works for me and wish to share as follows:
With the starting point of the reportlab example as regards to creating a toc containing document -which I assume you used in the first place- proceed as follows.
Modify the template assignment in the init function by adding the options onPage and onPageEnd defining calls to functions for drawing a header and a footer onto the page as follows, guess you could do the same within one function only, however, I used two in order to separate the header/footer section in my usecase:
template = PageTemplate('normal', [Frame(2.5*cm, 2.5*cm, 15*cm, 25*cm, id='F1')],onPage=header,onPageEnd=footer)
add the named header/footer functions to your class as shown below with illustrating some options for clarification including the page number in the footer function to make sure the question is answered:
def header(canvas, doc):
canvas.saveState()
canvas.drawImage('some_image.gif', 5,780, width=None,height=None,mask=None)
canvas.setLineWidth(1)
canvas.line(5,780,120,780)
canvas.setFont('Times-Bold',16)
canvas.drawString(108, 108, 'blah')
canvas.restoreState()
def footer(canvas, doc):
canvas.saveState()
canvas.setFont('Times-Roman',9)
canvas.drawImage('py.jpg', inch,inch, width=None,height=None,mask=None)
canvas.drawString(inch, 0.75 * inch, "Page %d " % doc.page)
canvas.restoreState()
Whatever you did with the added 'numbered canvas class' and why it breaks I don't know, but since your provided code reminds me strongly of the reportlab toc example I guess you can start with that, apply my suggested changes/additions and see how it goes.
That should do the trick, hope that helps!

Related

Django: Problem with ContentFile: String content is not downloaded completely

I'm quite new to using Django. As first project I wrote a little tool to create M3U8-Playlists.
My problem is, that long playlists are not transferred completely.
This is the stripped-down code:
def create(request):
# just a dummy filelist
playlist = ["#EXTM3U"] + 5 * ["/home/pi/music/" + 5 * "äöü0123456789á/" + "xyz.mp3"]
file_to_send = ContentFile("")
for item in playlist:
file_to_send.write("{}\n".format(item.replace("/home/pi/Music", r"\\raspberry\music").replace("/", "\\")))
response = HttpResponse(file_to_send, "audio/x-mpegurl")
response["Content-Length"] = file_to_send.size
response["Content-Disposition"] = f"attachment; filename=\"playlist.m3u8\""
# print some debug info
print("lines:", len(playlist), "chars (no linebreaks)", sum([len(entry) for entry in playlist]),
"filesize:", file_to_send.size)
return response
The problem seems to lie in the non-ascii chars in playlist entries (äöüá). When there are no such characters, the file is transferred intact. I assume that these are characters that use two bytes in UTF-8, but writing strings to the ContentFile like I do is probably not correct.
Found the answer, while working on the problem description.
This works:
def create(request):
# just a dummy filelist
playlist = ["#EXTM3U"] + 5 * ["/home/pi/music/" + 5 * "äöü0123456789á/" + "xyz.mp3"]
joined_playlist = "\n".join([item.replace("/home/pi/Music", r"\\raspberry\music").replace("/", "\\") for item in playlist])
file_to_send = ContentFile(joined_playlist.encode("UTF-8"))
response = HttpResponse(file_to_send, "audio/x-mpegurl")
response["Content-Length"] = file_to_send.size
response["Content-Disposition"] = f"attachment; filename=\"playlist.m3u8\""
# print some debug info
print("lines:", len(playlist), "chars (no linebreaks)", sum([len(entry) for entry in playlist]),
"filesize:", file_to_send.size)
return response
The important difference is, that I don't write Strings to the ContentFile any longer, but a byte array, which I got through encoding the String in UTF-8.
HTH

Generating a data table to iterate through in a Django Template

I have this function that uses PrettyTables to gather information about the Virtual Machines owned by a user. Right now, it only shows information and it works well. I have a new idea where I want to add a button to a new column which allows the user to reboot the virutal machine. I already know how to restart the virtual machines but what I'm struggling to figure out is the best way to create a dataset which i can iterate through and then create a HTML table. I've done similar stuff with PHP/SQL in the past and it was straight forward. I don't think I can iterate through PrettyTables so I'm wondering what is my best option? Pretty tables does a very good job of making it simple to create the table (as you can see below). I'm hoping to use another method, but also keep it very simple. Basically, making it relational and easy to iterate through. Any other suggestions are welcome. Thanks!
Here is my current code:
x = PrettyTable()
x.field_names = ["VM Name", "OS", "IP", "Power State"]
for uuid in virtual_machines:
vm = search_index.FindByUuid(None, uuid, True, False)
if vm.summary.guest.ipAddress == None:
ip = "Unavailable"
else:
ip = vm.summary.guest.ipAddress
if vm.summary.runtime.powerState == "poweredOff":
power_state = "OFF"
else:
power_state = "ON"
if vm.summary.guest.guestFullName == None:
os = "Unavailable"
else:
os = vm.summary.guest.guestFullName
x.add_row([vm.summary.config.name, os, ip, power_state])
table = x.get_html_string(attributes = {"class":"table table-striped"})
return table
Here is a sample of what it looks like and also what I plan to do with the button. http://prntscr.com/nki3ci
Figured out how to query the prettytable. It was a minor addition without having to redo it all.
html = '<table class="table"><tr><th>VM Name</th><th>OS</th><th>IP</th><th>Power
State</th></tr>'
htmlend = '</tr></table>'
body = ''
for vmm in x:
vmm.border = False
vmm.header = False
vm_name = (vmm.get_string(fields=["VM Name"]))
operating_system = (vmm.get_string(fields=["OS"]))
ip_addr = ((vmm.get_string(fields=["IP"])))
body += '<tr><td>'+ vm_name + '</td><td>' + operating_system + '</td> <td>'+ ip_addr +'</td> <td>ON</td></tr>'
html += body
html += htmlend
print(html)

Adding Progress Bar to gdal.Warp()

I am trying to figure out a way to use progress bar in gdal.Warp() to show how much of a job is done. For progress bar, I am using Tqdm and gdal.Warp() is used to crop image from remote URL
def getSubArea(url):
vsicurl_url = '/vsicurl/' + url
output_file = 'someID_subarea.tif'
gdal.SetConfigOption('GDAL_HTTP_UNSAFESSL', 'YES')
gdal.Warp(output_file, vsicurl_url, dstSRS='EPSG:4326', cutlineDSName='area.geojson', cropToCutline=True)
I know there is callback argument that reports progress from 0 to 1, but its only called after gdal.warp has finished downloading cropped image.
You may add a callback function for progress through the 'kwargs' parameter in 'gdal.Warp' (documentation: https://gdal.org/python/).
Code:
def getSubArea(url):
vsicurl_url = '/vsicurl/' + url
output_file = 'someID_subarea.tif'
# Data you want to pass to your callback (goes in to unknown parameter)
es_obj = { ... }
kwargs = {
'dstSRS': 'EPSG:4326',
'cutlineDSName': 'area.geojson',
'cropToCutline': True,
'callback': progress_callback,
'callback_data': es_obj
}
gdal.SetConfigOption('GDAL_HTTP_UNSAFESSL', 'YES')
gdal.Warp(output_file, vsicurl_url, **kwargs)
def progress_callback(self, complete, message, unknown):
# Calculate percent by integer values (1, 2, ..., 100)
percent = floor(complete * 100)
# Code for saving or using percent value
...
About progress callback: https://gdal.org/api/cpl.html#_CPPv416GDALProgressFunc
See answer here around native gdal callback function.
If you wanted to report the download progress of the raster, you'd likely need to split that out as a separate step using something like requests and wrapping that with a progress bar like tqdm or progressbar2.

how to replace a text on XML with lxml?

I'm trying to troncate several element.text on a xml file. I succeed to get two list, the first one regroup the formers too long element.text as str (long_name) and the second regroup the same after a troncation (short_name).
Now i want to replace the element.text on my xml, i tried some script but i surrended to work with the function readlines(), i want to find a similar solution with lxml as this code :
txt = open('IF_Generic.arxml','r')
Lines = txt.readlines()
txt.close()
txt = open('IF_Genericnew.arxml','w')
for e in range(len(long_name)) :
for i in range(len(Lines)) :
if (long_name[e] in Lines[i]) == True :
Lines[i] = Lines[i].replace(long_name[e],short_name[e])
for i in Lines :
txt.write(i)
txt.close()
I tried this, but it doesn't work :
f = open('IF_Generic.arxml')
arxml = f.read()
f.close()
tree = etree.parse(StringIO(arxml))
for e,b in enumerate(long_name) :
context = etree.iterparse(StringIO(arxml))
for a,i in context:
if not i.text:
pass
else:
if (b in i.text) == True :
i.text = short_name[e]
obj_arxml = etree.tostring(tree,pretty_print=True)
f = open('IF_Genericnew.arxml','w')
f.write(obj_arxml)
f.close()
Let's say the first element of the list long_name is RoutineServices_EngMGslLim_NVMID03
<BALISE_A>
<BALISE_B>
<SHORT-NAME>RoutineServices_EngMGslLim_NVMID03</SHORT-NAME>
</BALISE_B>
</BALISE_A>
<BALISE_C>
<POSSIBLE-ERROR-REF DEST="APPLICATION-ERROR">/Interfaces/RoutineServices_EngMGslLim_NVMID03/E_NOT_OK</POSSIBLE-ERROR-REF>
<SHORT-NAME>Blah_Bleh_Bluh</SHORT-NAME>
</BALISE_C>
The first element of the list short_name is RoutineServices_EngMGslLim_NV
<BALISE_A>
<BALISE_B>
<SHORT-NAME>RoutineServices_EngMGslLim_NV</SHORT-NAME>
</BALISE_B>
</BALISE_A>
<BALISE_C>
<POSSIBLE-ERROR-REF DEST="APPLICATION-ERROR">/Interfaces/RoutineServices_EngMGslLim_NV/E_NOT_OK</POSSIBLE-ERROR-REF>
<SHORT-NAME>Blah_Bleh_Bluh</SHORT-NAME>
</BALISE_C>
I want this
P.S: I use python 2.7.9
Thanks in advance everyone !
Don't open XML files like text files. I have explained in this answer why this is a bad idea.
Simply let etree read and write the file. It's also less code to write.
from lxml import etree
# read the file and load it into a DOM tree
tree = etree.parse('IF_Generic.arxml')
for elem in tree.iterfind("//*"):
# find elements that contain only text
if len(elem) == 0 and elem.text and elem.text.strip() > '':
# do your replacements ...
elem.text = "new text"
# serialize the DOM tree and write it to file
tree.write('IF_Genericnew.arxml', pretty_print=True)
Instead of going over all elements, which is what "//*" does, you can use more specific XPath to narrow down the elements you want to work on.
For example, something like "//SHORT-NAME | //POSSIBLE-ERROR-REF" would help to reduce the overall work load.

if self.request.GET[key] fetch value of key?

I'm trying to get the value of a key in the URL (localhost:8080?color=red) and display different pages according to what value is in the url (red or blue...etc). I'm not sure how to set that up, though, because nothing I've tried works. Any ideas? It's for a class assignment, so I can't use query_string.
if self.request.GET(['color','red']):
view = ContentPage()
fetch_data = Data()
name = fetch_data.red.name
description = fetch_data.red.description
self.response.write(view.print_first() + name + description + view.print_end())
else:
see = Page()
self.response.write(see.output())
Use self.request.get, like this:
color = self.request.get('color')
# do something with color