Merge Christoph Holterman's 'PR-python2to3-str_methods-gnc_jinja' into maint

This commit is contained in:
John Ralls 2019-04-14 14:23:37 -07:00
commit f772b50542
2 changed files with 99 additions and 106 deletions

71
bindings/python/example_scripts/gncinvoice_jinja.py Normal file → Executable file
View File

@ -1,16 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
##@file ##@file
# @brief exports an invoice from gnucash using a template file, see \ref py_invoice_export # @brief exports an invoice from gnucash using a template file, see \ref py_invoice_export
# @ingroup python_bindings_examples # @ingroup python_bindings_examples
# @author Christoph Holtermann (c.holtermann (at) gmx.de) # @author Christoph Holtermann (c.holtermann (at) gmx.de)
# @date 2014-11 # @date 2014-2018
# #
# @details # @details
# Input is a template file that will be filled with information from # Input is a template file that will be filled with information from
# gnucash Invoices. Jinja2 templating engine ist used. Templates can # gnucash Invoices. Jinja2 templating engine is being used for this.
# be Latex, Html or anything. # Templates can be Latex, Html or anything.
# #
# Example templates for german invoices: # Example templates for german invoices:
# - Invoice.tex.tmpl # - Invoice.tex.tmpl
@ -32,6 +32,7 @@
try: try:
import locale import locale
import os
import sys import sys
import getopt import getopt
import gnucash import gnucash
@ -60,10 +61,13 @@ def main(argv=None):
list_invoices = False list_invoices = False
invoice_number = None invoice_number = None
invoice_id = None invoice_id = None
filename_from_invoice = False
output_path = None
with_ipshell = False
try: try:
opts, args = getopt.getopt(argv[1:], "fhlI:t:o:", ["help"]) opts, args = getopt.getopt(argv[1:], "fhliI:t:o:OP:", ["help"])
except getopt.error, msg: except getopt.error as msg:
raise Usage(msg) raise Usage(msg)
for opt in opts: for opt in opts:
@ -74,16 +78,27 @@ def main(argv=None):
raise Usage("Help:") raise Usage("Help:")
if opt[0] in ["-I"]: if opt[0] in ["-I"]:
invoice_id = opt[1] invoice_id = opt[1]
print("using invoice ID '" + str(invoice_id) + "'.") print ("using invoice ID '" + str(invoice_id) + "'.")
if opt[0] in ["-i"]:
print ("Using ipshell")
with_ipshell = True
if opt[0] in ["-o"]: if opt[0] in ["-o"]:
filename_output = opt[1] filename_output = opt[1]
print("using output file", filename_output) print("using output file", filename_output)
if opt[0] in ["-O"]:
if filename_output:
print ("given output filename will be overwritten,")
print ("creating output filename from Invoice data.")
filename_from_invoice = True
if opt[0] in ["-t"]: if opt[0] in ["-t"]:
filename_template = opt[1] filename_template = opt[1]
print("using template file", filename_template) print("using template file", filename_template)
if opt[0] in ["-l"]: if opt[0] in ["-l"]:
list_invoices = True list_invoices = True
print("listing invoices") print("listing invoices")
if opt[0] in ["-P"]:
output_path = opt[1]
print ("output path is", output_path + ".")
# Check for correct input # Check for correct input
if len(args)>1: if len(args)>1:
@ -100,17 +115,17 @@ def main(argv=None):
raise Usage("No template given !") raise Usage("No template given !")
# Check for output file # Check for output file
if not filename_output: if not (filename_output or filename_from_invoice):
if filename_template: if filename_template:
filename_output = filename_template + ".out" filename_output = filename_template + ".out"
print("no output filename given, will be:", filename_output) print("no output filename given, will be:", filename_output)
except Usage, err: except Usage as err:
if err.msg == "Help:": if err.msg == "Help:":
retcode=0 retcode=0
else: else:
print(>>sys.stderr, "Error:",err.msg) print("Error:", err.msg, file=sys.stderr)
print(>>sys.stderr, "for help use --help") print("for help use --help", file=sys.stderr)
retcode=2 retcode=2
print() print()
@ -128,6 +143,8 @@ def main(argv=None):
print("-I ID use invoice ID") print("-I ID use invoice ID")
print("-t filename use filename as template file") print("-t filename use filename as template file")
print("-o filename use filename as output file") print("-o filename use filename as output file")
print("-O create output filename by date, owner and invoice number")
print("-P path path for output file. Overwrites path in -o option")
return retcode return retcode
@ -166,17 +183,35 @@ def main(argv=None):
print("Using the following invoice:") print("Using the following invoice:")
print(invoice) print(invoice)
loader = jinja2.FileSystemLoader('.')
path_template = os.path.dirname(filename_template)
filename_template_basename = os.path.basename(filename_template)
loader = jinja2.FileSystemLoader(path_template)
env = jinja2.Environment(loader=loader) env = jinja2.Environment(loader=loader)
template = env.get_template(filename_template) template = env.get_template(filename_template_basename)
#import IPython #company = gnucash_business.Company(book.instance)
#IPython.embed()
output = template.render(invoice=invoice, locale=locale)
print("Writing output", filename_output, ".") output = template.render(invoice=invoice, locale=locale) #, company=company)
if filename_from_invoice:
filename_date = invoice.GetDatePosted().strftime("%Y-%m-%d") # something like 2014-11-01
filename_owner_name = str(invoice.GetOwner().GetName())
filename_invoice_id = str(invoice.GetID())
filename_output = filename_date + "_" + filename_owner_name + "_" + filename_invoice_id + ".tex"
if output_path:
filename_output = os.path.join(output_path, os.path.basename(filename_output))
print ("Writing output", filename_output, ".")
with open(filename_output, 'w') as f: with open(filename_output, 'w') as f:
f.write(output.encode('utf-8')) f.write(output)
if with_ipshell:
import IPython
IPython.embed()
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@ -1,10 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python3
## @file ## @file
# @brief Add __str__ and __unicode__ methods to financial objects so that @code print object @endcode leads to human readable results # @brief Add __str__ methods to financial objects so that @code print object @endcode leads to human readable results
""" @package str_methods.py -- Add __str__ and __unicode__ methods to financial objects """ @package str_methods.py -- Add __str__ methods to financial objects
Import this module and str(Object) and unicode(Object) where Object is Transaction, Split,Invoice Import this module and str(Object) where Object is Transaction, Split, Invoice or Entry leads to
or Entry leads to human readable results. That is handy when using @code print object @endcode human readable results. That is handy when using @code print object @endcode
I chose to put these functions/methods in a separate file to develop them like this and maybe if I chose to put these functions/methods in a separate file to develop them like this and maybe if
they prove to be useful they can be put in gnucash_core.py. they prove to be useful they can be put in gnucash_core.py.
@ -30,7 +30,8 @@
# * It seems useful to have an object for each modification. That is because there is some Initialisation to be done. # * It seems useful to have an object for each modification. That is because there is some Initialisation to be done.
# #
import gnucash, function_class import gnucash
from gnucash import function_class
# Default values for encoding of strings in GnuCashs Database # Default values for encoding of strings in GnuCashs Database
DEFAULT_ENCODING = "UTF-8" DEFAULT_ENCODING = "UTF-8"
@ -149,29 +150,29 @@ class ClassWithCutting__format__():
value=self.value value=self.value
# Replace Tabs and linebreaks # Replace Tabs and linebreaks
import types #import types
if type(value) in [types.StringType, types.UnicodeType]: if isinstance(value, str):
value=value.replace("\t","|") value = value.replace("\t","|")
value=value.replace("\n","|") value = value.replace("\n","|")
# Do regular formatting of object # Do regular formatting of object
value=value.__format__(fmt) value = value.__format__(fmt)
# Cut resulting value if longer than specified by width # Cut resulting value if longer than specified by width
width=get_width(fmt) width = get_width(fmt)
if width: if width:
value=cut(value, width, "...") value = cut(value, width, "...")
return value return value
def all_as_classwithcutting__format__(*args): def all_as_classwithcutting__format__(*args):
"""Converts every argument to instance of ClassWithCutting__format__""" """Converts every argument to instance of ClassWithCutting__format__"""
import types #import types
l=[] l=[]
for a in args: for a in args:
if type(a) in [types.StringType, types.UnicodeType]: #if type(a) in [types.StringType, types.UnicodeType]:
a=a.decode("UTF-8") # a=a.decode("UTF-8")
l.append(ClassWithCutting__format__(a)) l.append(ClassWithCutting__format__(a))
return l return l
@ -179,15 +180,15 @@ def all_as_classwithcutting__format__(*args):
def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys): def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
"""Converts every argument to instance of ClassWithCutting__format__""" """Converts every argument to instance of ClassWithCutting__format__"""
import types #import types
d={} d={}
if encoding==None: if encoding==None:
encoding=DEFAULT_ENCODING encoding=DEFAULT_ENCODING
if error==None: if error==None:
error=DEFAULT_ERROR error=DEFAULT_ERROR
for a in keys: for a in keys:
if type(keys[a]) in [types.StringType, types.UnicodeType]: #if isinstance(keys[a], str):
keys[a]=keys[a].decode(encoding,error) # keys[a]=keys[a].decode(encoding,error)
d[a]=ClassWithCutting__format__(keys[a]) d[a]=ClassWithCutting__format__(keys[a])
return d return d
@ -195,8 +196,8 @@ def all_as_classwithcutting__format__keys(encoding=None, error=None, **keys):
# Split # Split
def __split__unicode__(self, encoding=None, error=None): def __split__str__(self, encoding=None, error=None):
"""__unicode__(self, encoding=None, error=None) -> object """__str__(self, encoding=None, error=None) -> object
Serialize the Split object and return as a new Unicode object. Serialize the Split object and return as a new Unicode object.
@ -228,40 +229,31 @@ def __split__unicode__(self, encoding=None, error=None):
"memo":self.GetMemo(), "memo":self.GetMemo(),
"lot":lot_str} "lot":lot_str}
fmt_str= (u"Account: {account:20} "+ fmt_str= ("Account: {account:20} "+
u"Value: {value:>10} "+ "Value: {value:>10} "+
u"Memo: {memo:30} ") "Memo: {memo:30} ")
if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]: if self.optionflags & self.OPTIONFLAGS_BY_NAME["PRINT_TRANSACTION"]:
fmt_t_dict={ fmt_t_dict={
"transaction_time":time.ctime(transaction.GetDate()), "transaction_time":time.ctime(transaction.GetDate()),
"transaction2":transaction.GetDescription()} "transaction2":transaction.GetDescription()}
fmt_t_str=( fmt_t_str=(
u"Transaction: {transaction_time:30} "+ "Transaction: {transaction_time:30} "+
u"- {transaction2:30} "+ "- {transaction2:30} "+
u"Lot: {lot:10}") "Lot: {lot:10}")
fmt_dict.update(fmt_t_dict) fmt_dict.update(fmt_t_dict)
fmt_str += fmt_t_str fmt_str += fmt_t_str
return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict)) return fmt_str.format(**all_as_classwithcutting__format__keys(encoding,error,**fmt_dict))
def __split__str__(self):
"""Returns a bytestring representation of self.__unicode__"""
from gnucash import Split
#self=Split(instance=self)
return unicode(self).encode('utf-8')
# This could be something like an __init__. Maybe we could call it virus because it infects the Split object which # This could be something like an __init__. Maybe we could call it virus because it infects the Split object which
# thereafter mutates to have better capabilities. # thereafter mutates to have better capabilities.
infect(gnucash.Split,__split__str__,"__str__") infect(gnucash.Split,__split__str__,"__str__")
infect(gnucash.Split,__split__unicode__,"__unicode__")
gnucash.Split.register_optionflag("PRINT_TRANSACTION") gnucash.Split.register_optionflag("PRINT_TRANSACTION")
gnucash.Split.setflag("PRINT_TRANSACTION",True) gnucash.Split.setflag("PRINT_TRANSACTION",True)
def __transaction__unicode__(self): def __transaction__str__(self):
"""__unicode__ method for Transaction class""" """__str__ method for Transaction class"""
from gnucash import Transaction from gnucash import Transaction
import time import time
self=Transaction(instance=self) self=Transaction(instance=self)
@ -270,7 +262,7 @@ def __transaction__unicode__(self):
'Description:',self.GetDescription(), 'Description:',self.GetDescription(),
'Notes:',self.GetNotes()) 'Notes:',self.GetNotes())
transaction_str = u"{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format( transaction_str = "{0:6}{1:25} {2:14}{3:40} {4:7}{5:40}".format(
*all_as_classwithcutting__format__(*fmt_tuple)) *all_as_classwithcutting__format__(*fmt_tuple))
transaction_str += "\n" transaction_str += "\n"
@ -281,29 +273,18 @@ def __transaction__unicode__(self):
transaction_flag = split.getflag("PRINT_TRANSACTION") transaction_flag = split.getflag("PRINT_TRANSACTION")
split.setflag("PRINT_TRANSACTION",False) split.setflag("PRINT_TRANSACTION",False)
splits_str += u"[{0:>2}] ".format(unicode(n)) splits_str += "[{0:>2}] ".format(str(n))
splits_str += unicode(split) splits_str += str(split)
splits_str += "\n" splits_str += "\n"
split.setflag("PRINT_TRANSACTION",transaction_flag) split.setflag("PRINT_TRANSACTION",transaction_flag)
return transaction_str + splits_str return transaction_str + splits_str
def __transaction__str__(self):
"""__str__ method for Transaction class"""
from gnucash import Transaction
self=Transaction(instance=self)
return unicode(self).encode('utf-8')
# These lines add transaction_str as method __str__ to Transaction object
gnucash.gnucash_core_c.__transaction__str__=__transaction__str__ gnucash.gnucash_core_c.__transaction__str__=__transaction__str__
gnucash.Transaction.add_method("__transaction__str__","__str__") gnucash.Transaction.add_method("__transaction__str__","__str__")
gnucash.gnucash_core_c.__transaction__unicode__=__transaction__unicode__ def __invoice__str__(self):
gnucash.Transaction.add_method("__transaction__unicode__","__unicode__") """__str__ method for Invoice"""
def __invoice__unicode__(self):
"""__unicode__ method for Invoice"""
from gnucash.gnucash_business import Invoice from gnucash.gnucash_business import Invoice
self=Invoice(instance=self) self=Invoice(instance=self)
@ -323,37 +304,27 @@ def __invoice__unicode__(self):
"total_value":str(self.GetTotal()), "total_value":str(self.GetTotal()),
"currency_mnemonic":self.GetCurrency().get_mnemonic()} "currency_mnemonic":self.GetCurrency().get_mnemonic()}
ret_invoice= (u"{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+ ret_invoice= ("{id_name:4}{id_value:10} {notes_name:7}{notes_value:20} {active_name:8}{active_value:7} {owner_name:12}{owner_value:20}"+
u"{total_name:8}{total_value:10}{currency_mnemonic:3}").\ "{total_name:8}{total_value:10}{currency_mnemonic:3}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict)) format(**all_as_classwithcutting__format__keys(**fmt_dict))
ret_entries=u"" ret_entries=""
entry_list = self.GetEntries() entry_list = self.GetEntries()
for entry in entry_list: # Type of entry has to be checked for entry in entry_list: # Type of entry has to be checked
if not(type(entry)==Entry): if not(type(entry)==Entry):
entry=Entry(instance=entry) entry=Entry(instance=entry)
ret_entries += " "+unicode(entry)+"\n" ret_entries += " "+str(entry)+"\n"
return ret_invoice+"\n"+ret_entries return ret_invoice+"\n"+ret_entries
def __invoice__str__(self):
"""__str__ method for invoice class"""
from gnucash.gnucash_business import Invoice
self=Invoice(instance=self)
return unicode(self).encode('utf-8')
from gnucash.gnucash_business import Invoice from gnucash.gnucash_business import Invoice
gnucash.gnucash_core_c.__invoice__str__=__invoice__str__ gnucash.gnucash_core_c.__invoice__str__=__invoice__str__
gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__") gnucash.gnucash_business.Invoice.add_method("__invoice__str__","__str__")
gnucash.gnucash_core_c.__invoice__unicode__=__invoice__unicode__ def __entry__str__(self):
gnucash.gnucash_business.Invoice.add_method("__invoice__unicode__","__unicode__") """__str__ method for Entry"""
def __entry__unicode__(self):
"""__unicode__ method for Entry"""
from gnucash.gnucash_business import Entry from gnucash.gnucash_business import Entry
self=Entry(instance=self) self=Entry(instance=self)
@ -361,34 +332,21 @@ def __entry__unicode__(self):
# This dict and the return statement can be changed according to individual needs # This dict and the return statement can be changed according to individual needs
fmt_dict={ fmt_dict={
"date_name":"Date:", "date_name":"Date:",
"date_value":unicode(self.GetDate()), "date_value":str(self.GetDate()),
"description_name":"Description:", "description_name":"Description:",
"description_value":self.GetDescription(), "description_value":self.GetDescription(),
"notes_name":"Notes:", "notes_name":"Notes:",
"notes_value":self.GetNotes(), "notes_value":self.GetNotes(),
"quant_name":"Quantity:", "quant_name":"Quantity:",
"quant_value":unicode(self.GetQuantity()), "quant_value":str(self.GetQuantity()),
"invprice_name":"InvPrice:", "invprice_name":"InvPrice:",
"invprice_value":unicode(self.GetInvPrice())} "invprice_value":str(self.GetInvPrice())}
return (u"{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+ return ("{date_name:6}{date_value:15} {description_name:13}{description_value:20} {notes_name:7}{notes_value:20}"+
u"{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\ "{quant_name:12}{quant_value:7} {invprice_name:10}{invprice_value:7}").\
format(**all_as_classwithcutting__format__keys(**fmt_dict)) format(**all_as_classwithcutting__format__keys(**fmt_dict))
def __entry__str__(self):
"""__str__ method for Entry class"""
from gnucash.gnucash_business import Entry
self=Entry(instance=self)
return unicode(self).encode('utf-8')
from gnucash.gnucash_business import Entry from gnucash.gnucash_business import Entry
gnucash.gnucash_core_c.__entry__str__=__entry__str__ gnucash.gnucash_core_c.__entry__str__=__entry__str__
gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__") gnucash.gnucash_business.Entry.add_method("__entry__str__","__str__")
gnucash.gnucash_core_c.__entry__unicode__=__entry__unicode__
gnucash.gnucash_business.Entry.add_method("__entry__unicode__","__unicode__")