Reportlab: header with data from page - python

I'm using the on page function and a page template to make headers for a subset of the pages in my document:
templates.append(PageTemplate(id='Overview', frames=frame, onPage=HeaderOverview))
The header function for this template:
################################
# Function HeaderOverview - header for overview page
def HeaderOverview(canvas,doc):
canvas.saveState()
headboxh = 15
headboxx = 20
headboxy = 730
headboxw = 570
canvas.rect(headboxx, headboxy, headboxw, headboxh, fill=1)
canvas.setFillColor(colors.black)
canvas.setFont("Helvetica", 14)
canvas.setFillColor(colors.white)
canvas.drawString(headboxx + 15,headboxy+.25*headboxh,"Mathematics")
textWidth = stringWidth("Mathematics", "Helvetica", 12)
canvas.setFont("Helvetica", 12)
canvas.drawString(headboxw - 15 - textWidth,headboxy+.25*headboxh,course)
canvas.restoreState()
This works great, except that the course variable that's passed (which changes with each page in the section) is the last one in the sequence, since this function's not really called until the final build (I think that's how it works). What I need is to do this so that the value is the value that's on the page. If I could draw it as I write the page itself, that'd be fine, too. Here's my attempt at that:
####################################################################################
# Function makeGradeOverview(course): makes Overview chart for grade
#
def makeGradeOverview(canvas, course):
report.append(NextPageTemplate("Overview"))
report.append(PageBreak())
headboxh = 50
headboxx = 20
headboxy = 600#730
headboxw = 540
canvas.saveState()
canvas.setFont("Helvetica", 12)
textWidth = stringWidth(course, "Helvetica", 12)
canvas.drawString(headboxw - 15 - textWidth,headboxy+.25*headboxh,course)
canvas.restoreState()
# put course name as title
if len(course)<=2:
headerrow = ''.join(['Grade ', course, ' Overview'])
else:
headerrow = ''.join([course, ' Overview'])
report.append(Paragraph(headerrow, styles["Overview Title"]))
report.append(Spacer(1, 16))
GridInfo = []
topics = topiclist(course)
for topic in topics:
report.append(Paragraph(topic, styles["Overview Sub"]))
report.append(Spacer(1, 8))
subtopics = subtopiclist(course, topic)
sublist = []
for subtopic in subtopics:
report.append(Paragraph(''.join([r'<bullet>&bull</bullet>',subtopic]), styles["Overview Table"]))
This doesn't throw an error or anything, but it doesn't seem to actually draw anything, either.
Thanks for the help!

Here's another idea...
Perhaps it would work to use specific flowables that can be identified to update the course. You can add custom attributes to flowables if necessary to help identify them (see this post).
For example, you might be able to do something like this:
...
report.append(some_content)
report.append(PageBreak())
report[-1].new_course = True # gives that PageBreak flowable a custom attribute
report.append(some_more_content)
...
And set up some variables:
course_list = [...]
course_iter = iter(course_list)
current_course = next(course_iter)
Then you can check each flowable after it is rendered to see if it has that attribute and update the current course if it does.
def afterFlowable(flowable):
global current_course
if hasattr(flowable, 'new_course'):
current_course = next(course_iter)
doc.afterFlowable = afterFlowable
HeaderOverview will be able to use the current_course variable to get the right course, since both HeaderOverview and afterFlowable are called at various points during the final build.

Related

Confused regarding the usage of -init_ & self in a class

I first wrote the necessary code to get the information I wanted from the internet, and it works. But now I'm trying to make the code look a bit nicer, therefore I want to put it into functions that are in a class. But I'm a bit confused when it comes to the usages of self and _init_. Currently, the code isn't working as I want, meaning it isn't adding the information to my dictionary.
As I have understood, you have to add self as a parameter in every function you create in a class. But I don't think I'm using the _init_ in a correct way.
from bs4 import BeautifulSoup
import requests
# Importing data from Nasdaq
page_link = "https://www.nasdaq.com/symbol/aapl/financials?query=balance-sheet"
page_response = requests.get(page_link, timeout=1000)
page_content = BeautifulSoup(page_response.content, "lxml")
# Creating class that gather essential stock information
class CompanySheet:
# creating dictionary to store stock information
def __init__(self):
self.stockInfo = {
"ticker": "",
"sharePrice": "",
"assets": "",
"liabilities": "",
"shareholderEquity": ""
}
def ticker(self):
# Finding ticker
self.tickerSymbol = page_content.find("div", attrs={"class":"qbreadcrumb"})
self.a_TickerList = self.tickerSymbol.findAll("a")
self.a_TickerList = (self.a_TickerList[2].text)
# Adding ticker to dictionary
self.stockInfo["ticker"] = self.a_TickerList
print(self.a_TickerList)
def share(self):
# Finding share price
self.sharePrice = page_content.findAll("div", attrs={"id":"qwidget_lastsale"})
self.aSharePrice = (self.sharePrice[0].text)
# Transforming share price to desired format
self.aSharePrice = str(self.aSharePrice[1:]).replace( ',' , '' )
self.aSharePrice = float(self.aSharePrice)
# Adding results to dictionary
self.stockInfo["sharePrice"] = self.aSharePrice
"""
def assets(self):
# Finding total assets
totalAssets = page_content.findAll("tr", attrs={"class":"net"})[1]
td_assetList = totalAssets.findAll("td")
tdAssets = (td_assetList[22].text)
# Transforming share price to desired format
tdAssets = str(tdAssets[1:]).replace( ',' , '' )
tdAssets = float(tdAssets)
# Adding results to dictionary
self.stockInfo["assets"] = tdAssets
def liabilites(self):
# Finding total liabilities
totalLiabilities = page_content.findAll("tr", attrs={"class":"net"})[3]
td_liabilityList = totalLiabilities.findAll("td")
tdLiabilities = (td_liabilityList[24].text)
# Transforming share price to desired format
tdLiabilities = str(tdLiabilities[1:]).replace( ',' , '' )
tdLiabilities = float(tdLiabilities)
# Adding results to dictionary
self.stockInfo["liabilities"] = tdLiabilities
def equity(self):
# Finding shareholder equity
netEquity = page_content.findAll("tr", attrs={"class":"net"})[4]
td_equityList = netEquity.findAll("td")
tdShareholderEquity = (td_equityList[24].text)
# Transforming shareholder equity to desired format
tdShareholderEquity = str(tdShareholderEquity[1:]).replace( ',' , '' )
tdShareholderEquity = float(tdShareholderEquity)
# Adding results to dictionary
self.stockInfo["shareholderEquity"] = tdShareholderEquity
"""
companySheet = CompanySheet()
print(companySheet.stockInfo)
All I want the code to do is for each function to parse it's information to my dictionary. I then want to access it outside of the class. Can someone help to clarify how I can use _init_ in this scenario, or do I even have to use it?
init is a constructor, which is called along with the creation of the class object. Whereas, self is an instance of the class, which is used accessing methods and attributes of a python class.
In your code, firstly change:
_init_(self) to __init__(self)
Then, in the methods:
def share(self):
# Finding share price
sharePrice = page_content.findAll("div", attrs={"id":"qwidget_lastsale"})
self.aSharePrice = (sharePrice[0].text)
# Transforming share price to desired format
self.aSharePrice = str(aSharePrice[1:]).replace( ',' , '' )
self.aSharePrice = float(aSharePrice)
# Adding results to dictionary
self.stockInfo["sharePrice"] = self.aSharePrice
Similarly, in all the remaining methods, access the variable through the self keyword.
Now, you also need to call the methods which are updating your dictionary.
So, after you have created the object, call the methods through the object and then print the dictionary, like this:
companySheet = CompanySheet()
companySheet.share()
print(companySheet.stockInfo)
Probably it would work!

How to insert a Libreoffice writer Annotation with a date using Python

Please note, this is a self answered question for reference.
Most of the references to com.sun.star.text.textfield.Annotation refer to Date as being a :: com :: sun :: star :: util :: reference but no end of fiddling about with the contents will actually create an annotation with a date.
Setting Date.Year, Date.Month and Date.Day will appear successful but the annotation itself still appears without a date i.e.
anno = model.createInstance("com.sun.star.text.textfield.Annotation")
anno.Content = "this is my annotation/comment"
anno.Author = doc.DocumentProperties.Author
anno.Date.Year = 2020
anno.Date.Month = 5
anno.Date.Day = 18
The documentation is not always complete, or is not obvious, depending upon where you look.
For LibreOffice 6.0 https://api.libreoffice.org/docs/idl/ref/Annotation_8idl_source.html
The Annotation.idl is described as:
service com::sun::star::text::TextField;
[property]string Author;
[optional, property]string Initials;
[optional, property]string Name;
[property]string Content;
[property]com::sun::star::util::Date Date;
[optional, property]com::sun::star::util::DateTime DateTimeValue;
The key here is the optional DateTimeValue which it would appear is the item that needs to be set to provide the Annotation with a Date and Time.
DateTimeValue structure is from com.sun.star.util.DateTime
To create an annotation (with a date and time) in a writer document using a python script, use the following as a template.
from uno import createUnoStruct
import time
def fs_Annotation(*args):
#get the doc from the scripting context which is made available to all scripts
desktop = XSCRIPTCONTEXT.getDesktop()
model = desktop.getCurrentComponent()
try:
text = model.Text
except:
# The macro has been called externally but LibreOffice was not running at the time
return None
tRange = text.End
cursor = desktop.getCurrentComponent().getCurrentController().getViewCursor()
doc = XSCRIPTCONTEXT.getDocument()
# you cannot insert simple text and text into a table with the same method
# so we have to know if we are in a table or not.
# oTable and oCurCell will be null if we are not in a table
oTable = cursor.TextTable
oCurCell = cursor.Cell
anno = model.createInstance("com.sun.star.text.textfield.Annotation")
anno.Content = "this is my annotation/comment"
#Use documents author
#anno.Author = doc.DocumentProperties.Author
#Use hardcoded text
anno.Author = "Joe Bloggs"
t = time.localtime()
dtv=createUnoStruct("com.sun.star.util.DateTime")
dtv.Year = t.tm_year
dtv.Month = t.tm_mon
dtv.Day = t.tm_mday
dtv.Hours = t.tm_hour
dtv.Minutes= t.tm_min
dtv.Seconds = t.tm_sec
dtv.NanoSeconds = 0
anno.DateTimeValue = dtv
if oCurCell == None: # Inserting into text
text.insertTextContent(cursor, anno, True)
else: # Inserting into a table
oCurCell.insertTextContent(cursor, anno, False)
return None
Section 7.7.2 of Andrew's macro document gives the following, although I have not tested it.
Sub AddNoteAtCursor
Dim vDoc, vViewCursor, oCurs, vTextField
Dim s$
'Lets lie and say that this was added ten days ago!'
Dim aDate As New com.sun.star.util.Date
With aDate
.Day = Day(Now - 10)
.Month = Month(Now - 10)
.Year = Year(Now - 10)
End With
vDoc = ThisComponent
vViewCursor = vDoc.getCurrentController().getViewCursor()
oCurs=vDoc.getText().createTextCursorByRange(vViewCursor.getStart())
s = "com.sun.star.text.TextField.Annotation"
vTextField = vDoc.createInstance(s)
With vTextField
.Author = "AP"
.Content = "It sure is fun to insert notes into my document"
'Ommit the date and it defaults to today!'
.Date = aDate
End With
vDoc.Text.insertTextContent(oCurs, vTextField, False)
End Sub
The API docs contain the same information as the IDL file but are somewhat easier to read.
https://www.openoffice.org/api/docs/common/ref/com/sun/star/text/textfield/Annotation.html

How to iterate over GQLQuery objects when an attribute exists for only some of them

So I'm building a basic blog where I've also implemented an IP address API to store the user's location. I've used this website's API to get the latitude and longitude of the user. This is the Entries class which is used to store each entry -
class Entries(db.Model):
title = db.StringProperty(required = True)
body = db.TextProperty(required = True)
created = db.DateTimeProperty(auto_now_add = True)
coords = db.GeoPtProperty()
This is the function to store the latitude and longitude in the coords property -
IP_URL = 'http://ip-api.com/xml/'
def get_coords(ip):
ip = '4.2.2.2'
url = IP_URL + ip
content = None
try:
content = urllib2.urlopen(url).read()
except urllib2.URLError:
return
if content:
# parse the XML and find the coordinates
dom = minidom.parseString(content)
status = dom.getElementsByTagName("status")[0].childNodes[0].nodeValue
if status == 'success':
lonNode = dom.getElementsByTagName('lon')[0]
latNode = dom.getElementsByTagName('lat')[0]
if lonNode and latNode and lonNode.childNodes[0].nodeValue and latNode.childNodes[0].nodeValue:
lon = lonNode.childNodes[0].nodeValue
lat = latNode.childNodes[0].nodeValue
return db.GeoPt(lat, lon)
When I go to the admin page (localhost:8000), I can see that some entries have the coords property while others don't. (To be precise, I have 8 entries currently, 6 of which have nothing in the coords column, 1 has the coordinates of 4.2.2.2 and the other has None, because I had passed in my localhost address.)
I have a main page where I list all the entries in the blog and it's here that I tried to see whether I can actually get all the coordinates stored in the database, to be displayed on the page -
class MainPage(webapp2.RequestHandler):
def get(self):
# self.response.write(self.request.remote_addr)
# self.response.write(repr(get_coords(self.request.remote_addr)))
entries = db.GqlQuery('select * from Entries order by created desc limit 10')
# find which entries have coordinates
points = []
for e in entries:
if entries.coords:
points.append(a.coords)
self.response.write(repr(points))
self.response.write(render_str('mainpage.html', entries=entries))
I tried printing out the property itself - self.response.write(repr(get_coords(self.request.remote_addr))) and it showed datastore_types.GeoPt(40.7128, -74.0059). So it seems the property IS stored in Datastore.
The problem I have is that the interpreter throws an AttributeError for the loop in the get of MainPage()-
AttributeError: 'GqlQuery' object has no attribute 'coords'
I think this may be because some of the entries do not have a coords attribute in the first place. But if this is the case, is there any way around it?
If it's of any use, I'm doing this for the Web Development course on Udacity by Steve Huffman and he showed this method of storing and displaying the user coordinates. He instructed to NOT use required=True for the coords property as the already existing entries don't have that property. When he looped over the entries in his blog, everything seemed fine and all the coordinates were displayed as a list.
I managed to solve the problem.
I used a filter instead of the for loop inside MainPage.
points = filter(None, (e.coords for e in entries))
This seemed to work and displayed the list of coordinates. (Though I still do not understand why this method worked)
Edit: I found out why I got the problem with my original code. It should have been e.coords, rather than entries.coords.

docutils/sphinx custom directive creating sibling section rather than child

Consider a reStructuredText document with this skeleton:
Main Title
==========
text text text text text
Subsection
----------
text text text text text
.. my-import-from:: file1
.. my-import-from:: file2
The my-import-from directive is provided by a document-specific Sphinx extension, which is supposed to read the file provided as its argument, parse reST embedded in it, and inject the result as a section in the current input file. (Like autodoc, but for a different file format.) The code I have for that, right now, looks like this:
class MyImportFromDirective(Directive):
required_arguments = 1
def run(self):
src, srcline = self.state_machine.get_source_and_line()
doc_file = os.path.normpath(os.path.join(os.path.dirname(src),
self.arguments[0]))
self.state.document.settings.record_dependencies.add(doc_file)
doc_text = ViewList()
try:
doc_text = extract_doc_from_file(doc_file)
except EnvironmentError as e:
raise self.error(e.filename + ": " + e.strerror) from e
doc_section = nodes.section()
doc_section.document = self.state.document
# report line numbers within the nested parse correctly
old_reporter = self.state.memo.reporter
self.state.memo.reporter = AutodocReporter(doc_text,
self.state.memo.reporter)
nested_parse_with_titles(self.state, doc_text, doc_section)
self.state.memo.reporter = old_reporter
if len(doc_section) == 1 and isinstance(doc_section[0], nodes.section):
doc_section = doc_section[0]
# If there was no title, synthesize one from the name of the file.
if len(doc_section) == 0 or not isinstance(doc_section[0], nodes.title):
doc_title = nodes.title()
doc_title.append(make_title_text(doc_file))
doc_section.insert(0, doc_title)
return [doc_section]
This works, except that the new section is injected as a child of the current section, rather than a sibling. In other words, the example document above produces a TOC tree like this:
Main Title
Subsection
File1
File2
instead of the desired
Main Title
Subsection
File1
File2
How do I fix this? The Docutils documentation is ... inadequate, particularly regarding control of section depth. One obvious thing I have tried is returning doc_section.children instead of [doc_section]; that completely removes File1 and File2 from the TOC tree (but does make the section headers in the body of the document appear to be for the right nesting level).
I don't think it is possible to do this by returning the section from the directive (without doing something along the lines of what Florian suggested), as it will get appended to the 'current' section. You can, however, add the section via self.state.section as I do in the following (handling of options removed for brevity)
class FauxHeading(object):
"""
A heading level that is not defined by a string. We need this to work with
the mechanics of
:py:meth:`docutils.parsers.rst.states.RSTState.check_subsection`.
The important thing is that the length can vary, but it must be equal to
any other instance of FauxHeading.
"""
def __init__(self, length):
self.length = length
def __len__(self):
return self.length
def __eq__(self, other):
return isinstance(other, FauxHeading)
class ParmDirective(Directive):
required_arguments = 1
optional_arguments = 0
has_content = True
option_spec = {
'type': directives.unchanged,
'precision': directives.nonnegative_int,
'scale': directives.nonnegative_int,
'length': directives.nonnegative_int}
def run(self):
variableName = self.arguments[0]
lineno = self.state_machine.abs_line_number()
secBody = None
block_length = 0
# added for some space
lineBlock = nodes.line('', '', nodes.line_block())
# parse the body of the directive
if self.has_content and len(self.content):
secBody = nodes.container()
block_length += nested_parse_with_titles(
self.state, self.content, secBody)
# keeping track of the level seems to be required if we want to allow
# nested content. Not sure why, but fits with the pattern in
# :py:meth:`docutils.parsers.rst.states.RSTState.new_subsection`
myLevel = self.state.memo.section_level
self.state.section(
variableName,
'',
FauxHeading(2 + len(self.options) + block_length),
lineno,
[lineBlock] if secBody is None else [lineBlock, secBody])
self.state.memo.section_level = myLevel
return []
I don't know how to do it directly inside your custom directive. However, you can use a custom transform to raise the File1 and File2 nodes in the tree after parsing. For example, see the transforms in the docutils.transforms.frontmatter module.
In your Sphinx extension, use the Sphinx.add_transform method to register the custom transform.
Update: You can also directly register the transform in your directive by returning one or more instances of the docutils.nodes.pending class in your node list. Make sure to call the note_pending method of the document in that case (in your directive you can get the document via self.state_machine.document).

How to add total page count in PDF using reportlab

def analysis_report(request):
response = HttpResponse(mimetype='application/pdf')
response['Content-Disposition'] = 'attachment;filename=ANALYSIS_REPORT.pdf'
buffer = StringIO()
doc = SimpleDocTemplate(buffer)
doc.sample_no = 12345
document = []
doc.build(document, onLaterPages=header_footer)
def header_footer(canvas, doc):
canvas.saveState()
canvas.setFont("Times-Bold", 11)
canvas.setFillColor(gray)
canvas.setStrokeColor('#5B80B2')
canvas.drawCentredString(310, 800, 'HEADER ONE GOES HERE')
canvas.drawString(440, 780, 'Sample No: %s' %doc.sample_no)
canvas.setFont('Times-Roman', 5)
canvas.drawString(565, 4, "Page %d" % doc.page)
I above code i can bale to display the page number, but my question is how can i display "Page X of Y" where Y is page count and X is current page.
I followed this http://code.activestate.com/recipes/546511-page-x-of-y-with-reportlab/, but they explained using canvasmaker, where as i'm using OnlaterPages argument in build.
How can i achieve the above thing using canvasmaker or is there any solution using OnLaterPages ?
Here is the improved recipe http://code.activestate.com/recipes/576832/ which should work with images.
Another possible workaround would be to use pyPDF (or any other pdf-lib with the funcionality) to read the total number of pages after doc.build() and then rebuild the story with the gathered information by exchanging the corresponding Paragraph()'s. This approach might be more hackish, but does the trick with no subclassing.
Example:
from pyPdf import PdfFileReader
[...]
story.append(Paragraph('temp paragraph. this will be exchanged with the total page number'))
post_story = story[:] #copy the story because build consumes it
doc.build(story) #build the pdf with name temp.pdf
temp_pdf = PdfFileReader(file("temp.pdf", "rb"))
total_pages = cert_temp.getNumPages()
post_story[-1] = Paragraph('total pages: ' + str(total_pages))
doc.build(post_story)

Categories

Resources