Merge github.com:web2py/web2py

Conflicts:
	gluon/contrib/login_methods/rpx_account.py
This commit is contained in:
Michele Comitini
2012-08-26 00:50:46 +02:00
136 changed files with 2898 additions and 1774 deletions
+34 -15
View File
@@ -2,25 +2,28 @@
### DAL Improvements
- MongoDB support in DAL (experimental)
- Support for DAL(lazy_tables=True) and db.define_table(on_define=lambda table:), thanks Jonathan
- MongoDB support in DAL (experimental), thanks Mark Breedveld
- geodal and spatialite, thanks Denes and Fran (experimental)
- db.mytable._before_insert, _after_insert, _before_update, _after_update, _before_delete. _after_delete (list of callbacks)
- db(...).update_naive(...) same as update but ignores table._before_update and table._after_update
- DAL BIGINT support and DAL(...,bigint_id=True)
- IS_IN_DB(..., distinct=True)
- new syntax: db.mytable.insert(myuploadfield=open(....))
- new syntax: db.mytable.insert(myuploadfield=open(....)), thank you Iceberg
- db(...).select(db.mytable.myfield.count(distinct=True))
- db(db.a)._update(name=db(db.b.a==db.a.id).nested_select(db.b.id))
- db.mytable.myfield.filter_in, filter_out
- db.mytable._enable_record_versioning(db) adds verining to this table
- db.mytable._enable_record_versioning(db) adds versioning to this table
- teradata adapter, thanks Andrew Willimott
- experimental Sybase Adapter
- added db.table.field.avg()
- Suport for Google App Engine projections
- Support for Google App Engine projections, thanks Christian
- Field(... 'upload', default=path) now accepts a path to a local file as default value, if user does not upload a file. Relative path looks inside current application folder, thanks Marin
- executesql(...,fields=,columns=) allows parsing of results in Rows, thanks Anthony
### Auth improvements
- auth.enable_record_versioning(db) adds full versining to all tables
- auth.enable_record_versioning(db) adds full versioning to all tables
- @auth.requires_login(otherwise=URL(...))
- auth supports salt and compatible with third party data, thanks Dave Stoll
- CRYPT now defaults to pbkdf2(1000,20,sha1)
@@ -33,14 +36,14 @@
- FORM.confirm('Are you sure?',{'Back':URL(...)})
- SQLFORM.smartdictform(dict)
- form.add_button(value,link)
- SQLFORM.grid(groupby=...')
- SQLFORM.grid(groupby='...')
- fixed security issue with SQLFORM.grid and SQLFORM.smartgrid
- more export options in SQLFORM.grid and SQLFORM.smartgrid (html, xml, csv, ...)
### Admin improvements
- new admin pages: manage_students, bulk_regsiter, and progress reports
- increased secure admin against CSRF
- increased security in admin against CSRF
- experimental Git integration
- experimental OpenShift deployment
- multi-language pluralization engine
@@ -49,17 +52,32 @@
- Romanian translation for welcome, thanks ionel
- support for mercurial 2.6, thanks Vlad
### Scheduler Inprovements
### Scheduler Improvements (thanks to niphlod, ykessler, dhx, toomim)
- web2py.py -K myapp -X starts the myapp scheduler alongside the webserver
- tasks are marked EXPIRED (if stop_time passed)
- functions with no result don't end up in scheduler_run
- more options: web2py.py -E -b -L
- scheduler can now handle 10k tasks with 20 concurrent workers and no known issues (thanks to niphlod, ykessler, dhx, toomim)
- tasks can be found in the environment (no need to define the tasks parameter)
- max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power), discard_results to completely discard the results (if you don't need the output of the task), utc_time enables datetime calculations with UTC time, task_name is no longer required (filled automatically with function_name if found empty), uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided), stop_time has no default (previously was today+1), retry_failed to requeue automatically failed tasks, sync_output refreshes automatically the output (nice to report percentages)
- tasks can be DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds), TERMINATE (complete the current task and then die), KILL (kill ASAP), EXPIRED (if stop_time passed)
- scheduler can now handle 10k tasks with 20 concurrent workers and with no issues
- new params:
tasks can be found in the environment (no need to define the tasks parameter)
max_empty_runs kills the workers automatically if no new tasks are found in queue (nice for "spikes" of processing power)
discard_results to completely discard the results (if you don't need the output of the task)
utc_time enables datetime calculations with UTC time
- scheduler_task changes:
task_name is no longer required (filled automatically with function_name if found empty)
uuid makes easy to coordinate scheduler_task maintenance (filled automatically if not provided)
stop_time has no default (previously was today+1)
retry_failed to requeue automatically failed tasks
sync_output refreshes automatically the output (nice to report percentages)
- workers can be:
DISABLED (put to sleep and do nothing if not sending the heartbeat every 30 seconds)
TERMINATE (complete the current task and then die)
KILL (kill ASAP)
### Other Improvements
- DIV(..).elements(...replace=...), thanks Anthony
- new layout based on Twitter Bootstrap
- New generic views: generic.ics (Mac Mail Calendar) and generic.map (Google Maps)
- request.args(0,default=0, cast=int, otherwise=URL(...)), thanks Anthony
@@ -67,18 +85,19 @@
- routes in can redirect outside with routes_in=[('/path','303->http://..')]
- better memcache support
- improved spreadsheet, thanks Alan
- new interantionalization engine, thanks Vladyslav
- new internationalization engine, thanks Vladyslav
- pluralization engine, thanks Vladyslav
- new makrmin with supports for nested lists, <i>, <em>, autolinks, thanks Vladyslav
- new markmin with support for nested lists, <i>, <em>, autolinks, thanks Vladyslav
- new syntax: {{=BR()*5}}
- gluon.cache.lazy_cache decorator allows caching functions in modules
- .coffee and .less support in response.fields, thanks Sam Sheftel
- .coffee and .less support in response.files, thanks Sam Sheftel
- ldap certificate support
- pg8000 postgresql driver support (experimental)
- @cache('%(name)s%(args)s%(vars)s',5) and cache.autokey
- added tox.ini, thanks Marc
- web2py.py --run_system_tests, thanks Marc Abramowitz
- html.py (and web2py helpers) can be used without web2py dependencies
- new fpdf, thanks Mariano
## 1.99.5-1.99.7
+4 -4
View File
@@ -1,3 +1,7 @@
all:
echo "The Makefile is used to build the distribution."
echo "In order to run web2py you do not need to make anything."
echo "just run web2py.py"
clean:
rm -f httpserver.log
rm -f parameters*.py
@@ -13,10 +17,6 @@ clean:
find ./applications/examples/ -name '.*' -exec rm -f {} \;
find ./applications/welcome/ -name '.*' -exec rm -f {} \;
find ./ -name '*.pyc' -exec rm -f {} \;
all:
echo "The Makefile is used to build the distribution."
echo "In order to run web2py you do not need to make anything."
echo "just run web2py.py"
epydoc:
### build epydoc
rm -f -r applications/examples/static/epydoc/
+1 -1
View File
@@ -1 +1 @@
Version 2.00.0 (2012-08-22 19:47:08) dev
Version 2.00.0 (2012-08-25 16:19:46) dev
+2
View File
@@ -6,3 +6,5 @@
+1
View File
@@ -309,3 +309,4 @@ if __name__=='__main__':
+1
View File
@@ -6,3 +6,4 @@ def webapp_add_wsgi_middleware(app):
+11 -9
View File
@@ -20,7 +20,7 @@ try:
have_git = True
except ImportError:
have_git = False
GIT_MISSING = 'requires python-git module, but not installed'
GIT_MISSING = 'requires python-git module, but not installed or incompatible version'
from gluon.languages import (regex_language, read_possible_languages,
read_possible_plurals, lang_sampling,
@@ -206,7 +206,8 @@ def site():
elif form_create.accepted:
# create a new application
appname = cleanpath(form_create.vars.name)
if app_create(appname, request):
created, error = app_create(appname, request,info=True)
if created:
if MULTI_USER_MODE:
db.app.insert(name=appname,owner=auth.user.id)
log_progress(appname)
@@ -214,14 +215,15 @@ def site():
redirect(URL('design',args=appname))
else:
session.flash = \
T('unable to create application "%s" (it may exist already)',
form_create.vars.name)
DIV(T('unable to create application "%s"' % appname),
PRE(error))
redirect(URL(r=request))
elif form_update.accepted:
if (form_update.vars.url or '').endswith('.git'):
if not have_git:
session.flash = GIT_MISSING
redirect(URL(r=request))
target = os.path.join(apath(r=request),form_update.vars.name)
try:
new_repo = Repo.clone_from(form_update.vars.url,target)
@@ -336,7 +338,7 @@ def pack_plugin():
redirect(URL('plugin',args=request.args))
def upgrade_web2py():
dialog = FORM.confim(T('Upgrade'),
dialog = FORM.confirm(T('Upgrade'),
{T('Cancel'):URL('site')})
if dialog.accepted:
(success, error) = upgrade(request)
@@ -350,7 +352,7 @@ def upgrade_web2py():
def uninstall():
app = get_app()
dialog = FORM.confim(T('Uninstall'),
dialog = FORM.confirm(T('Uninstall'),
{T('Cancel'):URL('site')})
if dialog.accepted:
@@ -433,7 +435,7 @@ def delete():
if isinstance(sender, list): # ## fix a problem with Vista
sender = sender[0]
dialog = FORM.confim(T('Delete'),
dialog = FORM.confirm(T('Delete'),
{T('Cancel'):URL(sender, anchor=request.vars.id)})
if dialog.accepted:
@@ -1003,7 +1005,7 @@ def delete_plugin():
plugin = request.args(1)
plugin_name='plugin_'+plugin
dialog = FORM.confim(
dialog = FORM.confirm(
T('Delete'),
{T('Cancel'):URL('design', args=app)})
@@ -1652,7 +1654,7 @@ def git_pull():
if not have_git:
session.flash = GIT_MISSING
redirect(URL('site'))
dialog = FORM.confim(T('Pull'),
dialog = FORM.confirm(T('Pull'),
{T('Cancel'):URL('site')})
if dialog.accepted:
try:
+5 -5
View File
@@ -274,13 +274,13 @@ ul.button li a {
display: block;
z-index: 2;
}
a.button {
a.button, a.btn {
display: inline-block;
vertical-align: middle;
}
/* 1.1. Normal status */
a.button,
a.button, a.btn,
ul.button li a {
background-position: 100% 0;
background-repeat: no-repeat;
@@ -313,14 +313,14 @@ ul.button li.select a span {
/* 2. EDITABLE STYLES */
/* 2.1. Image background used */
a.button,
a.button, a.btn,
.button a,
.button span {
background-image: url(../images/button.png);
}
/* 2.2. Normal status (Example for padding 10px) */
a.button,
a.button, a.btn,
.button a {
padding: 0 10px 0 0; /* Padding-right: 10px */
margin: 0 1px 0 10px; /* Margin-left: 10px */
@@ -342,7 +342,7 @@ a.button span {
_float: left; /* Only IE6 */
_position: relative; /* Only IE6 */
}
a.button {
a.button, a.btn {
display: -moz-inline-box; /* FF<3 */
display: inline-block; /* FF<3 */
-moz-box-orient: vertical; /* FF<3 */
+9 -3
View File
@@ -80,11 +80,11 @@ fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4p
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:right}
td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0}
div.w2p_export_menu a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
@@ -294,8 +294,14 @@ div.error {
margin-bottom:18px;
}
.web2py_breadcrumbs ul li {
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
.ie9 #query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
@@ -78,6 +78,16 @@
</p>
</div>
{{pass}}
<!-- MULTI_USER_INTERFACE -->
{{if MULTI_USER_MODE and is_manager():}}
<div class="box">
<h3>{{=T("Multi User Mode")}}</h3>
<p>
{{=button(URL('bulk_register'),T('Bulk Register'))}}
{{=button(URL('manage_students'),T('Manage Students'))}}
</p>
</div>
{{pass}}
<!-- APP WIZARD -->
<div class="box">
<h3>{{=T("New application wizard")}}</h3>
@@ -7,6 +7,7 @@
- [[User Voice http://web2py.uservoice.com/ popup]]
#### Learning and Demos
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
- [[Welcome App Demo http://www.web2py.com/welcome]] (scaffolding application)
- [[Videos http://www.web2py.com/examples/default/videos/]]
+9 -3
View File
@@ -80,11 +80,11 @@ fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4p
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:right}
td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0}
div.w2p_export_menu a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
@@ -294,8 +294,14 @@ div.error {
margin-bottom:18px;
}
.web2py_breadcrumbs ul li {
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
.ie9 #query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
+8 -2
View File
@@ -80,11 +80,11 @@ fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4p
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:right}
td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0}
div.w2p_export_menu a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; padding:2px 5px; background-color:#f1f1f1; border-radius:5px; -moz-border-radius:5px; -webkit-border-radius:5px;}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
@@ -299,3 +299,9 @@ li.w2p_grid_breadcrumb_elem {
}
.ie9 #query_panel {padding-bottom:2px}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
@@ -1,4 +1,4 @@
{{left_sidebar_enabled,right_sidebar_enabled=False,True}}
{{left_sidebar_enabled,right_sidebar_enabled=False,('message' in globals())}}
{{extend 'layout.html'}}
{{if 'message' in globals():}}
@@ -17,6 +17,8 @@
_href=URL('admin','default','peek',args=(request.application,'views',request.controller,'index.html')))))}}</li>
<li>{{=T('You can modify this application and adapt it to your needs')}}</li>
</ol>
{{elif 'content' in globals():}}
{{=content}}
{{else:}}
{{=BEAUTIFY(response._vars)}}
{{pass}}
@@ -31,4 +33,3 @@
<li><a href="http://web2py.com/book">{{=T('Documentation')}}</a></li>
</ul>
{{end}}
+2 -7
View File
@@ -43,12 +43,8 @@
response.files.append(URL('static','css/bootstrap.min.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
response.files.append(URL('static','css/web2py.css'))
response.files.append(URL('static','css/bootswatch.css'))
response.files.append(URL('static','css/web2py_bootstrap.css'))
}}
<!-- Makes bootswatch working on IE 7/8 -->
<!--[if (lt IE 9)]>
<link rel="stylesheet" href="{{=URL('static','css/bootswatch_ie.css')}}">
<![endif]-->
{{include 'web2py_ajax.html'}}
@@ -64,7 +60,7 @@
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" type="text/css" media="all" />
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
uncomment to load jquery-ui //-->
<noscript><link href="{{=URL('static', 'css/bootswatch_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript>
<noscript><link href="{{=URL('static', 'css/web2py_bootstrap_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript>
{{block head}}{{end}}
</head>
@@ -160,7 +156,6 @@
if(jQuery(this).find('ul').length)
jQuery(this).children('a').contents().before('<i class="chevron-right"></i>');
});
if(jQuery(document).width()>=980) {
jQuery('ul.nav li.dropdown').hover(function() {
jQuery(this).find('.dropdown-menu').stop(true, true).delay(200).fadeIn();
+1
View File
@@ -65,3 +65,4 @@ wsgiref.handlers.CGIHandler().run(gluon.main.wsgibase)
+1
View File
@@ -55,3 +55,4 @@ fcgi.WSGIServer(application, bindAddress='/tmp/fcgi.sock').run()
+1
View File
@@ -103,3 +103,4 @@ if __name__ == '__main__':
+1
View File
@@ -52,3 +52,4 @@ if 0:
+25 -9
View File
@@ -18,6 +18,7 @@ from fileutils import up, fix_newlines, abspath, recursive_unlink
from fileutils import read_file, write_file, parse_version
from restricted import RestrictedError
from settings import global_settings
from http import HTTP
if not global_settings.web2py_runtime_gae:
import site
@@ -126,7 +127,7 @@ def app_cleanup(app, request):
r = False
# Remove cache files
path = apath('%s/sessions/' % app, request)
path = apath('%s/cache/' % app, request)
if os.path.exists(path):
for f in os.listdir(path):
try:
@@ -157,7 +158,7 @@ def app_compile(app, request):
remove_compiled_application(folder)
return tb
def app_create(app, request,force=False,key=None):
def app_create(app, request,force=False,key=None,info=False):
"""
Create a copy of welcome.w2p (scaffolding) app
@@ -169,11 +170,19 @@ def app_create(app, request,force=False,key=None):
the global request object
"""
try:
path = apath(app, request)
os.mkdir(path)
except:
if not force:
path = apath(app, request)
if not os.path.exists(path):
try:
os.mkdir(path)
except:
if info:
return False, traceback.format_exc(sys.exc_info)
else:
return False
elif not force:
if info:
return False, "Application exists"
else:
return False
try:
w2p_unpack('welcome.w2p', path)
@@ -189,10 +198,16 @@ def app_create(app, request,force=False,key=None):
data = data.replace('<your secret key>',
'sha512:'+(key or web2py_uuid()))
write_file(db, data)
return True
if info:
return True, None
else:
return True
except:
rmtree(path)
return False
if info:
return False, traceback.format_exc(sys.exc_info)
else:
return False
def app_install(app, fobj, request, filename, overwrite=None):
@@ -465,3 +480,4 @@ def create_missing_app_folders(request):
+1
View File
@@ -498,3 +498,4 @@ def lazy_cache(key=None,time_expire=None,cache_model='ram'):
return decorator
+1
View File
@@ -56,3 +56,4 @@ def getcfs(key, filename, filter=None):
+1
View File
@@ -691,3 +691,4 @@ if __name__ == '__main__':
+1
View File
@@ -723,3 +723,4 @@ def contenttype(filename, default='text/plain'):
+1
View File
@@ -263,3 +263,4 @@ if __name__=='__main__':
+1
View File
@@ -243,3 +243,4 @@ if __name__=='__main__':
+1
View File
@@ -5,3 +5,4 @@
+1
View File
@@ -501,3 +501,4 @@ aes_Rcon = array('B',
'61c29f254a943366cc831d3a74e8cb'.decode('hex')
)
+3 -2
View File
@@ -135,7 +135,7 @@ def oembed(url):
for k,v in EMBED_MAPS:
if k.match(url):
oembed = v+'?format=json&url='+cgi.escape(url)
try:
try:
data = urllib.urlopen(oembed).read()
print data
return loads(data) # json!
@@ -152,7 +152,7 @@ def expand_one(url,cdict):
r = cdict[url]
else:
r = oembed(url)
if isinstance(cdict,dict):
if isinstance(cdict,dict):
cdict[url] = r
# if oembed service
if 'html' in r:
@@ -204,3 +204,4 @@ if __name__=="__main__":
else:
print test()
+1
View File
@@ -194,3 +194,4 @@ if __name__ == "__main__":
+1
View File
@@ -3910,3 +3910,4 @@ def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, refer
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"FPDF for python"
__license__ = "LGPL 3.0"
__version__ = "1.7"
from fpdf import *
try:
from html import HTMLMixin
except ImportError:
import warnings
warnings.warn("web2py gluon package not installed, required for html2pdf")
from template import Template
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
# Fonts:
fpdf_charwidths = {}
fpdf_charwidths['courier']={}
for i in xrange(0,256):
fpdf_charwidths['courier'][chr(i)]=600
fpdf_charwidths['courierB']=fpdf_charwidths['courier']
fpdf_charwidths['courierI']=fpdf_charwidths['courier']
fpdf_charwidths['courierBI']=fpdf_charwidths['courier']
fpdf_charwidths['helvetica']={
'\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
'\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
'\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
'\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
'\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
fpdf_charwidths['helveticaB']={
'\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
'\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
'\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
'\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
'\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556
}
fpdf_charwidths['helveticaBI']={
'\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
'\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':333,'"':474,'#':556,'$':556,'%':889,'&':722,'\'':238,'(':333,')':333,'*':389,'+':584,
',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':333,';':333,'<':584,'=':584,'>':584,'?':611,'@':975,'A':722,
'B':722,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':556,'K':722,'L':611,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
'X':667,'Y':667,'Z':611,'[':333,'\\':278,']':333,'^':584,'_':556,'`':333,'a':556,'b':611,'c':556,'d':611,'e':556,'f':333,'g':611,'h':611,'i':278,'j':278,'k':556,'l':278,'m':889,
'n':611,'o':611,'p':611,'q':611,'r':389,'s':556,'t':333,'u':611,'v':556,'w':778,'x':556,'y':556,'z':500,'{':389,'|':280,'}':389,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':278,'\x83':556,
'\x84':500,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':278,'\x92':278,'\x93':500,'\x94':500,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':556,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':280,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
'\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':611,'\xb6':556,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':556,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':611,'\xf1':611,
'\xf2':611,'\xf3':611,'\xf4':611,'\xf5':611,'\xf6':611,'\xf7':584,'\xf8':611,'\xf9':611,'\xfa':611,'\xfb':611,'\xfc':611,'\xfd':556,'\xfe':611,'\xff':556}
fpdf_charwidths['helveticaI']={
'\x00':278,'\x01':278,'\x02':278,'\x03':278,'\x04':278,'\x05':278,'\x06':278,'\x07':278,'\x08':278,'\t':278,'\n':278,'\x0b':278,'\x0c':278,'\r':278,'\x0e':278,'\x0f':278,'\x10':278,'\x11':278,'\x12':278,'\x13':278,'\x14':278,'\x15':278,
'\x16':278,'\x17':278,'\x18':278,'\x19':278,'\x1a':278,'\x1b':278,'\x1c':278,'\x1d':278,'\x1e':278,'\x1f':278,' ':278,'!':278,'"':355,'#':556,'$':556,'%':889,'&':667,'\'':191,'(':333,')':333,'*':389,'+':584,
',':278,'-':333,'.':278,'/':278,'0':556,'1':556,'2':556,'3':556,'4':556,'5':556,'6':556,'7':556,'8':556,'9':556,':':278,';':278,'<':584,'=':584,'>':584,'?':556,'@':1015,'A':667,
'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':722,'I':278,'J':500,'K':667,'L':556,'M':833,'N':722,'O':778,'P':667,'Q':778,'R':722,'S':667,'T':611,'U':722,'V':667,'W':944,
'X':667,'Y':667,'Z':611,'[':278,'\\':278,']':278,'^':469,'_':556,'`':333,'a':556,'b':556,'c':500,'d':556,'e':556,'f':278,'g':556,'h':556,'i':222,'j':222,'k':500,'l':222,'m':833,
'n':556,'o':556,'p':556,'q':556,'r':333,'s':500,'t':278,'u':556,'v':500,'w':722,'x':500,'y':500,'z':500,'{':334,'|':260,'}':334,'~':584,'\x7f':350,'\x80':556,'\x81':350,'\x82':222,'\x83':556,
'\x84':333,'\x85':1000,'\x86':556,'\x87':556,'\x88':333,'\x89':1000,'\x8a':667,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':222,'\x92':222,'\x93':333,'\x94':333,'\x95':350,'\x96':556,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':500,'\x9b':333,'\x9c':944,'\x9d':350,'\x9e':500,'\x9f':667,'\xa0':278,'\xa1':333,'\xa2':556,'\xa3':556,'\xa4':556,'\xa5':556,'\xa6':260,'\xa7':556,'\xa8':333,'\xa9':737,'\xaa':370,'\xab':556,'\xac':584,'\xad':333,'\xae':737,'\xaf':333,
'\xb0':400,'\xb1':584,'\xb2':333,'\xb3':333,'\xb4':333,'\xb5':556,'\xb6':537,'\xb7':278,'\xb8':333,'\xb9':333,'\xba':365,'\xbb':556,'\xbc':834,'\xbd':834,'\xbe':834,'\xbf':611,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':278,'\xcd':278,'\xce':278,'\xcf':278,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':584,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':667,'\xde':667,'\xdf':611,'\xe0':556,'\xe1':556,'\xe2':556,'\xe3':556,'\xe4':556,'\xe5':556,'\xe6':889,'\xe7':500,'\xe8':556,'\xe9':556,'\xea':556,'\xeb':556,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':556,'\xf1':556,
'\xf2':556,'\xf3':556,'\xf4':556,'\xf5':556,'\xf6':556,'\xf7':584,'\xf8':611,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
fpdf_charwidths['symbol']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':713,'#':500,'$':549,'%':833,'&':778,'\'':439,'(':333,')':333,'*':500,'+':549,
',':250,'-':549,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':549,'=':549,'>':549,'?':444,'@':549,'A':722,
'B':667,'C':722,'D':612,'E':611,'F':763,'G':603,'H':722,'I':333,'J':631,'K':722,'L':686,'M':889,'N':722,'O':722,'P':768,'Q':741,'R':556,'S':592,'T':611,'U':690,'V':439,'W':768,
'X':645,'Y':795,'Z':611,'[':333,'\\':863,']':333,'^':658,'_':500,'`':500,'a':631,'b':549,'c':549,'d':494,'e':439,'f':521,'g':411,'h':603,'i':329,'j':603,'k':549,'l':549,'m':576,
'n':521,'o':549,'p':549,'q':521,'r':549,'s':603,'t':439,'u':576,'v':713,'w':686,'x':493,'y':686,'z':494,'{':480,'|':200,'}':480,'~':549,'\x7f':0,'\x80':0,'\x81':0,'\x82':0,'\x83':0,
'\x84':0,'\x85':0,'\x86':0,'\x87':0,'\x88':0,'\x89':0,'\x8a':0,'\x8b':0,'\x8c':0,'\x8d':0,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
'\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':750,'\xa1':620,'\xa2':247,'\xa3':549,'\xa4':167,'\xa5':713,'\xa6':500,'\xa7':753,'\xa8':753,'\xa9':753,'\xaa':753,'\xab':1042,'\xac':987,'\xad':603,'\xae':987,'\xaf':603,
'\xb0':400,'\xb1':549,'\xb2':411,'\xb3':549,'\xb4':549,'\xb5':713,'\xb6':494,'\xb7':460,'\xb8':549,'\xb9':549,'\xba':549,'\xbb':549,'\xbc':1000,'\xbd':603,'\xbe':1000,'\xbf':658,'\xc0':823,'\xc1':686,'\xc2':795,'\xc3':987,'\xc4':768,'\xc5':768,
'\xc6':823,'\xc7':768,'\xc8':768,'\xc9':713,'\xca':713,'\xcb':713,'\xcc':713,'\xcd':713,'\xce':713,'\xcf':713,'\xd0':768,'\xd1':713,'\xd2':790,'\xd3':790,'\xd4':890,'\xd5':823,'\xd6':549,'\xd7':250,'\xd8':713,'\xd9':603,'\xda':603,'\xdb':1042,
'\xdc':987,'\xdd':603,'\xde':987,'\xdf':603,'\xe0':494,'\xe1':329,'\xe2':790,'\xe3':790,'\xe4':786,'\xe5':713,'\xe6':384,'\xe7':384,'\xe8':384,'\xe9':384,'\xea':384,'\xeb':384,'\xec':494,'\xed':494,'\xee':494,'\xef':494,'\xf0':0,'\xf1':329,
'\xf2':274,'\xf3':686,'\xf4':686,'\xf5':686,'\xf6':384,'\xf7':384,'\xf8':384,'\xf9':384,'\xfa':384,'\xfb':384,'\xfc':494,'\xfd':494,'\xfe':494,'\xff':0}
fpdf_charwidths['times']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':408,'#':500,'$':500,'%':833,'&':778,'\'':180,'(':333,')':333,'*':500,'+':564,
',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':278,';':278,'<':564,'=':564,'>':564,'?':444,'@':921,'A':722,
'B':667,'C':667,'D':722,'E':611,'F':556,'G':722,'H':722,'I':333,'J':389,'K':722,'L':611,'M':889,'N':722,'O':722,'P':556,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':722,'W':944,
'X':722,'Y':722,'Z':611,'[':333,'\\':278,']':333,'^':469,'_':500,'`':333,'a':444,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':500,'i':278,'j':278,'k':500,'l':278,'m':778,
'n':500,'o':500,'p':500,'q':500,'r':333,'s':389,'t':278,'u':500,'v':500,'w':722,'x':500,'y':500,'z':444,'{':480,'|':200,'}':480,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
'\x84':444,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':889,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':444,'\x94':444,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':980,
'\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':200,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':564,'\xad':333,'\xae':760,'\xaf':333,
'\xb0':400,'\xb1':564,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':453,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':444,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
'\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':564,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':722,'\xde':556,'\xdf':500,'\xe0':444,'\xe1':444,'\xe2':444,'\xe3':444,'\xe4':444,'\xe5':444,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
'\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':564,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':500,'\xfe':500,'\xff':500}
fpdf_charwidths['timesB']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':555,'#':500,'$':500,'%':1000,'&':833,'\'':278,'(':333,')':333,'*':500,'+':570,
',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':930,'A':722,
'B':667,'C':722,'D':722,'E':667,'F':611,'G':778,'H':778,'I':389,'J':500,'K':778,'L':667,'M':944,'N':722,'O':778,'P':611,'Q':778,'R':722,'S':556,'T':667,'U':722,'V':722,'W':1000,
'X':722,'Y':722,'Z':667,'[':333,'\\':278,']':333,'^':581,'_':500,'`':333,'a':500,'b':556,'c':444,'d':556,'e':444,'f':333,'g':500,'h':556,'i':278,'j':333,'k':556,'l':278,'m':833,
'n':556,'o':500,'p':556,'q':556,'r':444,'s':389,'t':333,'u':556,'v':500,'w':722,'x':500,'y':500,'z':444,'{':394,'|':220,'}':394,'~':520,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
'\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':1000,'\x8d':350,'\x8e':667,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':444,'\x9f':722,'\xa0':250,'\xa1':333,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':300,'\xab':500,'\xac':570,'\xad':333,'\xae':747,'\xaf':333,
'\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':556,'\xb6':540,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':330,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':722,'\xc1':722,'\xc2':722,'\xc3':722,'\xc4':722,'\xc5':722,
'\xc6':1000,'\xc7':722,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':778,'\xd3':778,'\xd4':778,'\xd5':778,'\xd6':778,'\xd7':570,'\xd8':778,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':722,'\xde':611,'\xdf':556,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
'\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':500,'\xfe':556,'\xff':500}
fpdf_charwidths['timesBI']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':389,'"':555,'#':500,'$':500,'%':833,'&':778,'\'':278,'(':333,')':333,'*':500,'+':570,
',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':570,'=':570,'>':570,'?':500,'@':832,'A':667,
'B':667,'C':667,'D':722,'E':667,'F':667,'G':722,'H':778,'I':389,'J':500,'K':667,'L':611,'M':889,'N':722,'O':722,'P':611,'Q':722,'R':667,'S':556,'T':611,'U':722,'V':667,'W':889,
'X':667,'Y':611,'Z':611,'[':333,'\\':278,']':333,'^':570,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':333,'g':500,'h':556,'i':278,'j':278,'k':500,'l':278,'m':778,
'n':556,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':556,'v':444,'w':667,'x':500,'y':444,'z':389,'{':348,'|':220,'}':348,'~':570,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
'\x84':500,'\x85':1000,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':556,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':611,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':500,'\x94':500,'\x95':350,'\x96':500,'\x97':1000,'\x98':333,'\x99':1000,
'\x9a':389,'\x9b':333,'\x9c':722,'\x9d':350,'\x9e':389,'\x9f':611,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':220,'\xa7':500,'\xa8':333,'\xa9':747,'\xaa':266,'\xab':500,'\xac':606,'\xad':333,'\xae':747,'\xaf':333,
'\xb0':400,'\xb1':570,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':576,'\xb6':500,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':300,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':667,'\xc1':667,'\xc2':667,'\xc3':667,'\xc4':667,'\xc5':667,
'\xc6':944,'\xc7':667,'\xc8':667,'\xc9':667,'\xca':667,'\xcb':667,'\xcc':389,'\xcd':389,'\xce':389,'\xcf':389,'\xd0':722,'\xd1':722,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':570,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':611,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':722,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':556,
'\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':570,'\xf8':500,'\xf9':556,'\xfa':556,'\xfb':556,'\xfc':556,'\xfd':444,'\xfe':500,'\xff':444}
fpdf_charwidths['timesI']={
'\x00':250,'\x01':250,'\x02':250,'\x03':250,'\x04':250,'\x05':250,'\x06':250,'\x07':250,'\x08':250,'\t':250,'\n':250,'\x0b':250,'\x0c':250,'\r':250,'\x0e':250,'\x0f':250,'\x10':250,'\x11':250,'\x12':250,'\x13':250,'\x14':250,'\x15':250,
'\x16':250,'\x17':250,'\x18':250,'\x19':250,'\x1a':250,'\x1b':250,'\x1c':250,'\x1d':250,'\x1e':250,'\x1f':250,' ':250,'!':333,'"':420,'#':500,'$':500,'%':833,'&':778,'\'':214,'(':333,')':333,'*':500,'+':675,
',':250,'-':333,'.':250,'/':278,'0':500,'1':500,'2':500,'3':500,'4':500,'5':500,'6':500,'7':500,'8':500,'9':500,':':333,';':333,'<':675,'=':675,'>':675,'?':500,'@':920,'A':611,
'B':611,'C':667,'D':722,'E':611,'F':611,'G':722,'H':722,'I':333,'J':444,'K':667,'L':556,'M':833,'N':667,'O':722,'P':611,'Q':722,'R':611,'S':500,'T':556,'U':722,'V':611,'W':833,
'X':611,'Y':556,'Z':556,'[':389,'\\':278,']':389,'^':422,'_':500,'`':333,'a':500,'b':500,'c':444,'d':500,'e':444,'f':278,'g':500,'h':500,'i':278,'j':278,'k':444,'l':278,'m':722,
'n':500,'o':500,'p':500,'q':500,'r':389,'s':389,'t':278,'u':500,'v':444,'w':667,'x':444,'y':444,'z':389,'{':400,'|':275,'}':400,'~':541,'\x7f':350,'\x80':500,'\x81':350,'\x82':333,'\x83':500,
'\x84':556,'\x85':889,'\x86':500,'\x87':500,'\x88':333,'\x89':1000,'\x8a':500,'\x8b':333,'\x8c':944,'\x8d':350,'\x8e':556,'\x8f':350,'\x90':350,'\x91':333,'\x92':333,'\x93':556,'\x94':556,'\x95':350,'\x96':500,'\x97':889,'\x98':333,'\x99':980,
'\x9a':389,'\x9b':333,'\x9c':667,'\x9d':350,'\x9e':389,'\x9f':556,'\xa0':250,'\xa1':389,'\xa2':500,'\xa3':500,'\xa4':500,'\xa5':500,'\xa6':275,'\xa7':500,'\xa8':333,'\xa9':760,'\xaa':276,'\xab':500,'\xac':675,'\xad':333,'\xae':760,'\xaf':333,
'\xb0':400,'\xb1':675,'\xb2':300,'\xb3':300,'\xb4':333,'\xb5':500,'\xb6':523,'\xb7':250,'\xb8':333,'\xb9':300,'\xba':310,'\xbb':500,'\xbc':750,'\xbd':750,'\xbe':750,'\xbf':500,'\xc0':611,'\xc1':611,'\xc2':611,'\xc3':611,'\xc4':611,'\xc5':611,
'\xc6':889,'\xc7':667,'\xc8':611,'\xc9':611,'\xca':611,'\xcb':611,'\xcc':333,'\xcd':333,'\xce':333,'\xcf':333,'\xd0':722,'\xd1':667,'\xd2':722,'\xd3':722,'\xd4':722,'\xd5':722,'\xd6':722,'\xd7':675,'\xd8':722,'\xd9':722,'\xda':722,'\xdb':722,
'\xdc':722,'\xdd':556,'\xde':611,'\xdf':500,'\xe0':500,'\xe1':500,'\xe2':500,'\xe3':500,'\xe4':500,'\xe5':500,'\xe6':667,'\xe7':444,'\xe8':444,'\xe9':444,'\xea':444,'\xeb':444,'\xec':278,'\xed':278,'\xee':278,'\xef':278,'\xf0':500,'\xf1':500,
'\xf2':500,'\xf3':500,'\xf4':500,'\xf5':500,'\xf6':500,'\xf7':675,'\xf8':500,'\xf9':500,'\xfa':500,'\xfb':500,'\xfc':500,'\xfd':444,'\xfe':500,'\xff':444}
fpdf_charwidths['zapfdingbats']={
'\x00':0,'\x01':0,'\x02':0,'\x03':0,'\x04':0,'\x05':0,'\x06':0,'\x07':0,'\x08':0,'\t':0,'\n':0,'\x0b':0,'\x0c':0,'\r':0,'\x0e':0,'\x0f':0,'\x10':0,'\x11':0,'\x12':0,'\x13':0,'\x14':0,'\x15':0,
'\x16':0,'\x17':0,'\x18':0,'\x19':0,'\x1a':0,'\x1b':0,'\x1c':0,'\x1d':0,'\x1e':0,'\x1f':0,' ':278,'!':974,'"':961,'#':974,'$':980,'%':719,'&':789,'\'':790,'(':791,')':690,'*':960,'+':939,
',':549,'-':855,'.':911,'/':933,'0':911,'1':945,'2':974,'3':755,'4':846,'5':762,'6':761,'7':571,'8':677,'9':763,':':760,';':759,'<':754,'=':494,'>':552,'?':537,'@':577,'A':692,
'B':786,'C':788,'D':788,'E':790,'F':793,'G':794,'H':816,'I':823,'J':789,'K':841,'L':823,'M':833,'N':816,'O':831,'P':923,'Q':744,'R':723,'S':749,'T':790,'U':792,'V':695,'W':776,
'X':768,'Y':792,'Z':759,'[':707,'\\':708,']':682,'^':701,'_':826,'`':815,'a':789,'b':789,'c':707,'d':687,'e':696,'f':689,'g':786,'h':787,'i':713,'j':791,'k':785,'l':791,'m':873,
'n':761,'o':762,'p':762,'q':759,'r':759,'s':892,'t':892,'u':788,'v':784,'w':438,'x':138,'y':277,'z':415,'{':392,'|':392,'}':668,'~':668,'\x7f':0,'\x80':390,'\x81':390,'\x82':317,'\x83':317,
'\x84':276,'\x85':276,'\x86':509,'\x87':509,'\x88':410,'\x89':410,'\x8a':234,'\x8b':234,'\x8c':334,'\x8d':334,'\x8e':0,'\x8f':0,'\x90':0,'\x91':0,'\x92':0,'\x93':0,'\x94':0,'\x95':0,'\x96':0,'\x97':0,'\x98':0,'\x99':0,
'\x9a':0,'\x9b':0,'\x9c':0,'\x9d':0,'\x9e':0,'\x9f':0,'\xa0':0,'\xa1':732,'\xa2':544,'\xa3':544,'\xa4':910,'\xa5':667,'\xa6':760,'\xa7':760,'\xa8':776,'\xa9':595,'\xaa':694,'\xab':626,'\xac':788,'\xad':788,'\xae':788,'\xaf':788,
'\xb0':788,'\xb1':788,'\xb2':788,'\xb3':788,'\xb4':788,'\xb5':788,'\xb6':788,'\xb7':788,'\xb8':788,'\xb9':788,'\xba':788,'\xbb':788,'\xbc':788,'\xbd':788,'\xbe':788,'\xbf':788,'\xc0':788,'\xc1':788,'\xc2':788,'\xc3':788,'\xc4':788,'\xc5':788,
'\xc6':788,'\xc7':788,'\xc8':788,'\xc9':788,'\xca':788,'\xcb':788,'\xcc':788,'\xcd':788,'\xce':788,'\xcf':788,'\xd0':788,'\xd1':788,'\xd2':788,'\xd3':788,'\xd4':894,'\xd5':838,'\xd6':1016,'\xd7':458,'\xd8':748,'\xd9':924,'\xda':748,'\xdb':918,
'\xdc':927,'\xdd':928,'\xde':928,'\xdf':834,'\xe0':873,'\xe1':828,'\xe2':924,'\xe3':924,'\xe4':917,'\xe5':930,'\xe6':931,'\xe7':463,'\xe8':883,'\xe9':836,'\xea':836,'\xeb':867,'\xec':867,'\xed':696,'\xee':696,'\xef':874,'\xf0':0,'\xf1':874,
'\xf2':760,'\xf3':946,'\xf4':771,'\xf5':865,'\xf6':771,'\xf7':888,'\xf8':967,'\xf9':888,'\xfa':831,'\xfb':873,'\xfc':927,'\xfd':970,'\xfe':918,'\xff':0}
File diff suppressed because it is too large Load Diff
@@ -26,22 +26,23 @@ def hex2dec(color = "#000000"):
class HTML2FPDF(HTMLParser):
"Render basic HTML to FPDF"
def __init__(self, pdf, image_map, **kwargs):
def __init__(self, pdf):
HTMLParser.__init__(self)
self.image_map = image_map
self.style = {}
self.pre = False
self.href = ''
self.align = ''
self.page_links = {}
self.font_list = ("times","courier", "helvetica")
self.font = None
self.font_stack = []
self.pdf = pdf
self.r = self.g = self.b = 0
self.indent = 0
self.bullet = []
self.font_face="times" # initialize font
self.color=0 # initialize font color
self.set_font(kwargs.get("font","times"), kwargs.get("fontsize",12))
self.set_font("times", 12)
self.font_face = "times" # initialize font
self.color = 0 #initialize font color
self.table = None # table attributes
self.table_col_width = None # column (header) widths
self.table_col_index = None # current column index
@@ -66,7 +67,10 @@ class HTML2FPDF(HTMLParser):
def handle_data(self, txt):
if self.td is not None: # drawing a table?
if 'width' not in self.td and 'colspan' not in self.td:
l = [self.table_col_width[self.table_col_index]]
try:
l = [self.table_col_width[self.table_col_index]]
except IndexError:
raise RuntimeError("Table column/cell width not specified, unable to continue")
elif 'colspan' in self.td:
i = self.table_col_index
colspan = int(self.td['colspan'])
@@ -83,7 +87,7 @@ class HTML2FPDF(HTMLParser):
else:
self.set_style('B',True)
border = border or 'B'
align = self.td.get('align', 'C')[0].upper()
align = 'C'
bgcolor = hex2dec(self.td.get('bgcolor', self.tr.get('bgcolor', '')))
# parsing table header/footer (drawn later):
if self.thead is not None:
@@ -173,7 +177,7 @@ class HTML2FPDF(HTMLParser):
if tag=='p':
self.pdf.ln(5)
if attrs:
self.align=attrs['align'].lower()
if attrs: self.align = attrs.get('align')
if tag in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6'):
k = (2, 1.5, 1.17, 1, 0.83, 0.67)[int(tag[1])]
self.pdf.ln(5*k)
@@ -208,6 +212,8 @@ class HTML2FPDF(HTMLParser):
self.pdf.write(self.h,'%s%s ' % (' '*5*self.indent, bullet))
self.set_text_color()
if tag=='font':
# save previous font state:
self.font_stack.append((self.font_face, self.font_size, self.color))
if 'color' in attrs:
self.color = hex2dec(attrs['color'])
self.set_text_color(*color)
@@ -234,6 +240,7 @@ class HTML2FPDF(HTMLParser):
self.tfooter = []
self.thead = None
self.tfoot = None
self.table_h = 0
self.pdf.ln()
if tag=='tr':
self.tr = dict([(k.lower(), v) for k,v in attrs.items()])
@@ -244,7 +251,7 @@ class HTML2FPDF(HTMLParser):
if tag=='th':
self.td = dict([(k.lower(), v) for k,v in attrs.items()])
self.th = True
if self.td['width']:
if 'width' in self.td:
self.table_col_width.append(self.td['width'])
if tag=='thead':
self.thead = {}
@@ -258,8 +265,7 @@ class HTML2FPDF(HTMLParser):
h = px2mm(attrs.get('height',0))
if self.align and self.align[0].upper() == 'C':
x = (self.pdf.w-x)/2.0 - w/2.0
self.pdf.image(self.image_map(attrs['src']),
x, y, w, h, link=self.href)
self.pdf.image(attrs['src'], x, y, w, h, link=self.href)
self.pdf.set_x(x+w)
self.pdf.set_y(y+h)
if tag=='b' or tag=='i' or tag=='u':
@@ -324,12 +330,13 @@ class HTML2FPDF(HTMLParser):
self.td = None
self.th = False
if tag=='font':
if self.color:
# recover last font state
face, size, color = self.font_stack.pop()
if face:
self.pdf.set_text_color(0,0,0)
self.color = None
if self.font_face:
self.set_font('Times',12)
self.set_font(face, size)
self.font = None
if tag=='center':
self.align = None
@@ -381,79 +388,10 @@ class HTML2FPDF(HTMLParser):
self.pdf.line(self.pdf.get_x(),self.pdf.get_y(),self.pdf.get_x()+187,self.pdf.get_y())
self.pdf.ln(3)
class HTMLMixin():
def write_html(self, text, image_map=lambda x:x, **kwargs):
class HTMLMixin(object):
def write_html(self, text):
"Parse HTML and convert it to PDF"
h2p = HTML2FPDF(self,image_map=image_map,**kwargs)
h2p = HTML2FPDF(self)
h2p.feed(text)
if __name__=='__main__':
html="""
<H1 align="center">html2fpdf</H1>
<h2>Basic usage</h2>
<p>You can now easily print text mixing different
styles : <B>bold</B>, <I>italic</I>, <U>underlined</U>, or
<B><I><U>all at once</U></I></B>!<BR>You can also insert links
on text, such as <A HREF="http://www.fpdf.org">www.fpdf.org</A>,
or on an image: click on the logo.<br>
<center>
<A HREF="http://www.fpdf.org"><img src="tutorial/logo.png" width="104" height="71"></A>
</center>
<h3>Sample List</h3>
<ul><li>option 1</li>
<ol><li>option 2</li></ol>
<li>option 3</li></ul>
<table border="0" align="center" width="50%">
<thead><tr><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
<tbody>
<tr><td>cell 1</td><td>cell 2</td></tr>
<tr><td>cell 2</td><td>cell 3</td></tr>
</tbody>
</table>
<table border="1">
<thead><tr bgcolor="#A0A0A0"><th width="30%">Header 1</th><th width="70%">header 2</th></tr></thead>
<tfoot><tr bgcolor="#E0E0E0"><td>footer 1</td><td>footer 2</td></tr></tfoot>
<tbody>
<tr><td>cell 1</td><td>cell 2</td></tr>
<tr>
<td width="30%">cell 1</td><td width="70%" bgcolor="#D0D0FF" align='right'>cell 2</td>
</tr>
</tbody>
<tbody><tr><td colspan="2">cell spanned</td></tr></tbody>
<tbody>
""" + """<tr bgcolor="#F0F0F0">
<td>cell 3</td><td>cell 4</td>
</tr><tr bgcolor="#FFFFFF">
<td>cell 5</td><td>cell 6</td>
</tr>""" * 200 + """
</tbody>
</table>
"""
class MyFPDF(FPDF, HTMLMixin):
def header(self):
self.image('tutorial/logo_pb.png',10,8,33)
self.set_font('Arial','B',15)
self.cell(80)
self.cell(30,10,'Title',1,0,'C')
self.ln(20)
def footer(self):
self.set_y(-15)
self.set_font('Arial','I',8)
txt = 'Page %s of %s' % (self.page_no(), self.alias_nb_pages())
self.cell(0,10,txt,0,0,'C')
pdf=MyFPDF()
#First page
pdf.add_page()
pdf.write_html(html)
pdf.output('html.pdf','F')
import os
os.system("evince html.pdf")
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: latin-1 -*-
# fpdf php helpers:
def substr(s, start, length=-1):
if length < 0:
length=len(s)-start
return s[start:start+length]
def sprintf(fmt, *args): return fmt % args
def print_r(array):
if not isinstance(array, dict):
array = dict([(k, k) for k in array])
for k, v in array.items():
print "[%s] => %s" % (k, v),
def UTF8ToUTF16BE(instr, setbom=True):
"Converts UTF-8 strings to UTF16-BE."
outstr = ""
if (setbom):
outstr += "\xFE\xFF";
if not isinstance(instr, unicode):
instr = instr.decode('UTF-8')
outstr += instr.encode('UTF-16BE')
return outstr
def UTF8StringToArray(instr):
"Converts UTF-8 strings to codepoints array"
return [ord(c) for c in instr]
# ttfints php helpers:
def die(msg):
raise RuntimeError(msg)
def str_repeat(s, count):
return s * count
def str_pad(s, pad_length=0, pad_char= " ", pad_type= +1 ):
if pad_type<0: # pad left
return s.rjust(pad_length, pad_char)
elif pad_type>0: # pad right
return s.ljust(pad_length, pad_char)
else: # pad both
return s.center(pad_length, pad_char)
strlen = count = lambda s: len(s)
+52 -28
View File
@@ -16,7 +16,8 @@ class Template:
def __init__(self, infile=None, elements=None, format='A4', orientation='portrait',
title='', author='', subject='', creator='', keywords=''):
if elements:
self.elements = dict([(v['name'].lower(),v) for v in elements])
self.elements = elements
self.keys = [v['name'].lower() for v in self.elements]
self.handlers = {'T': self.text, 'L': self.line, 'I': self.image,
'B': self.rect, 'BC': self.barcode, }
self.pg_no = 0
@@ -32,34 +33,33 @@ class Template:
"Parse template format csv file and create elements dict"
keys = ('name','type','x1','y1','x2','y2','font','size',
'bold','italic','underline','foreground','background',
'align','text','priority')
self.elements = {}
f = open(infile, 'rb')
try:
for row in csv.reader(f, delimiter=delimiter):
kargs = {}
for i,v in enumerate(row):
if not v.startswith("'") and decimal_sep!=".":
v = v.replace(decimal_sep,".")
else:
v = v
if v=='':
v = None
else:
v = eval(v.strip())
kargs[keys[i]] = v
self.elements[kargs['name'].lower()] = kargs
finally:
f.close()
'align','text','priority', 'multiline')
self.elements = []
for row in csv.reader(open(infile, 'rb'), delimiter=delimiter):
kargs = {}
for i,v in enumerate(row):
if not v.startswith("'") and decimal_sep!=".":
v = v.replace(decimal_sep,".")
else:
v = v
if v=='':
v = None
else:
v = eval(v.strip())
kargs[keys[i]] = v
self.elements.append(kargs)
self.keys = [v['name'].lower() for v in self.elements]
def add_page(self):
self.pg_no += 1
self.texts[self.pg_no] = {}
def __setitem__(self, name, value):
if name.lower() in self.elements:
if self.has_key(name):
if isinstance(value,unicode):
value = value.encode("latin1","ignore")
elif value is None:
value = ""
else:
value = str(value)
self.texts[self.pg_no][name.lower()] = value
@@ -67,14 +67,27 @@ class Template:
# setitem shortcut (may be further extended)
set = __setitem__
def has_key(self, name):
return name.lower() in self.keys
def __getitem__(self, name):
if name.lower() in self.elements:
return self.texts[self.pg_no].get(name.lower(), self.elements[name.lower()]['text'])
if self.has_key(name):
key = name.lower()
if key in self.texts:
# text for this page:
return self.texts[self.pg_no][key]
else:
# find first element for default text:
elements = [element for element in self.elements
if element['name'].lower() == key]
if elements:
return elements[0]['text']
def split_multicell(self, text, element_name):
"Divide (\n) a string using a given element width"
pdf = self.pdf
element = self.elements[element_name.lower()]
element = [element for element in self.elements
if element['name'].lower() == element_name.lower()][0]
style = ""
if element['bold']: style += "B"
if element['italic']: style += "I"
@@ -96,7 +109,7 @@ class Template:
pdf.set_font('Arial','B',16)
pdf.set_auto_page_break(False,margin=0)
for element in sorted(self.elements.values(),key=lambda x: x['priority']):
for element in sorted(self.elements,key=lambda x: x['priority']):
#print "dib",element['type'], element['name'], element['x1'], element['y1'], element['x2'], element['y2']
element = element.copy()
element['text'] = self.texts[pg].get(element['name'].lower(), element['text'])
@@ -110,7 +123,7 @@ class Template:
def text(self, pdf, x1=0, y1=0, x2=0, y2=0, text='', font="arial", size=10,
bold=False, italic=False, underline=False, align="",
foreground=0, backgroud=65535,
foreground=0, backgroud=65535, multiline=None,
*args, **kwargs):
if text:
if pdf.text_color!=rgb(foreground):
@@ -134,7 +147,19 @@ class Template:
##m_k = 72 / 2.54
##h = (size/m_k)
pdf.set_xy(x1,y1)
pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
if multiline is None:
# multiline==None: write without wrapping/trimming (default)
pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
elif multiline:
# multiline==True: automatic word - warp
pdf.multi_cell(w=x2-x1,h=y2-y1,txt=text,border=0,align=align)
else:
# multiline==False: trim to fit exactly the space defined
text = pdf.multi_cell(w=x2-x1, h=y2-y1,
txt=text, align=align, split_only=True)[0]
print "trimming: *%s*" % text
pdf.cell(w=x2-x1,h=y2-y1,txt=text,border=0,ln=0,align=align)
#pdf.Text(x=x1,y=y1,txt=text)
def line(self, pdf, x1=0, y1=0, x2=0, y2=0, size=0, foreground=0, *args, **kwargs):
@@ -275,4 +300,3 @@ if __name__ == "__main__":
else:
os.system("./invoice.pdf")
File diff suppressed because it is too large Load Diff
+1
View File
@@ -55,3 +55,4 @@ class MemcacheClient(Client):
+1
View File
@@ -90,3 +90,4 @@ def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
+2 -1
View File
@@ -7,7 +7,7 @@ import gluon.serializers
from gluon import current, HTTP
from gluon.html import markmin_serializer, TAG, HTML, BODY, UL, XML, H1
from gluon.contenttype import contenttype
from gluon.contrib.pyfpdf import FPDF, HTMLMixin
from gluon.contrib.fpdf import FPDF, HTMLMixin
from gluon.sanitizer import sanitize
from gluon.contrib.markmin.markmin2latex import markmin2latex
from gluon.contrib.markmin.markmin2pdf import markmin2pdf
@@ -67,3 +67,4 @@ def pdf_from_html(html):
+1
View File
@@ -19,3 +19,4 @@ def button(merchant_id="123456789012345",
+1
View File
@@ -9,3 +9,4 @@ from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, driv
+1
View File
@@ -58,3 +58,4 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
else:
return image
+1
View File
@@ -1,2 +1,3 @@
@@ -23,3 +23,4 @@ def basic_auth(server="http://127.0.0.1"):
return False
return basic_login_aux
@@ -6,18 +6,18 @@
developed by Madhukar R Pai (Copyright 2012)
Email <madspai@gmail.com>
License : LGPL
thanks and credits to the web2py community
This custom authenticator allows web2py to authenticate using browserid (https://browserid.org/)
BrowserID is a project by Mozilla Labs (http://mozillalabs.com/)
to Know how browserid works please visit http://identity.mozilla.com/post/7616727542/introducing-browserid-a-better-way-to-sign-in
bottom line BrowserID provides a free, secure, de-centralized, easy to use(for users and developers) login solution.
You can use any email id as your login id. Browserid just verifys the email id and lets you login with that id.
credits for the doPost jquery function - itsadok (http://stackoverflow.com/users/7581/itsadok)
"""
import time
from gluon import *
@@ -32,7 +32,7 @@ class BrowserID(object):
audience = "http://127.0.0.1:8000"
assertion_post_url = "http://127.0.0.1:8000/%s/default/user/login" % request.application)
"""
def __init__(self,
request,
audience = "",
@@ -45,7 +45,7 @@ class BrowserID(object):
crypto_js = "https://crypto-js.googlecode.com/files/2.2.0-crypto-md5.js",
on_login_failure = None,
):
self.request = request
self.audience = audience
self.assertion_post_url = assertion_post_url
@@ -64,13 +64,13 @@ class BrowserID(object):
def get_user(self):
request = self.request
if request.vars.assertion:
if request.vars.assertion:
audience = self.audience
issuer = self.issuer
assertion = XML(request.vars.assertion,sanitize=True)
verify_data = {'assertion':assertion,'audience':audience}
auth_info_json = fetch(self.verify_url,data=verify_data)
j = json.loads(auth_info_json)
j = json.loads(auth_info_json)
epoch_time = int(time.time()*1000) # we need 13 digit epoch time
if j["status"] == "okay" and j["audience"] == audience and j['issuer'] == issuer and j['expires'] >= epoch_time:
return dict(email = j['email'])
@@ -88,3 +88,4 @@ class BrowserID(object):
A(IMG(_src=self.browserid_button,_alt=self.prompt),_href="#",_onclick=onclick,_class="browserid",_title="Login With BrowserID"),
SCRIPT(self.asertion_js))
return form
+1
View File
@@ -136,3 +136,4 @@ class CasAuth( object ):
import urllib
redirect("%s?service=%s" % (self.cas_logout_url,self.cas_my_url))
@@ -12,7 +12,7 @@
import os
import re
import urllib
from dropbox import client, rest, session
from dropbox import client, rest, session
from gluon import *
from gluon.tools import fetch
from gluon.storage import Storage
@@ -50,15 +50,15 @@ class DropboxAccount(object):
self.sess = session.DropboxSession(
self.key,self.secret,self.access_type)
def get_user(self):
request = self.request
token = current.session.dropbox_token
try:
access_token = self.sess.obtain_access_token(token)
access_token = self.sess.obtain_access_token(token)
except:
access_token = None
if access_token:
if access_token:
user = Storage()
self.client = client.DropboxClient(self.sess)
data = self.client.account_info()
@@ -68,7 +68,7 @@ class DropboxAccount(object):
last_name = display_name[-1],
registration_id = data.get('uid',None))
if not user['registration_id'] and self.on_login_failure:
redirect(self.on_login_failure)
redirect(self.on_login_failure)
return user
return None
@@ -107,3 +107,4 @@ def use_dropbox(auth,filename='private/dropbox.key',**kwargs):
auth.settings.login_form = DropboxAccount(
request,key=key,secret=secret,access_type=access_type,
login_url = login_url,**kwargs)
@@ -43,3 +43,4 @@ def email_auth(server="smtp.gmail.com:587",
pass
return False
return email_auth_aux
@@ -86,7 +86,7 @@ class ExtendedLoginForm(object):
Otherwise it will render the normal login form combined with
alt_login_form.login_form.
"""
request = current.request
args = request.args
@@ -102,3 +102,4 @@ class ExtendedLoginForm(object):
form.components.append(self.alt_login_form.login_form())
return form
@@ -36,3 +36,4 @@ class GaeGoogleAccount(object):
return dict(nickname=user.nickname(), email=user.email(),
user_id=user.user_id(), source="google account")
+1
View File
@@ -634,3 +634,4 @@ def ldap_auth(server='ldap', port=None,
if filterstr[0] == '(' and filterstr[-1] == ')': # rfc4515 syntax
filterstr = filterstr[1:-1] # parens added again where used
return ldap_auth_aux
@@ -49,3 +49,4 @@ class LinkedInAccount(object):
username = profile.id)
+1
View File
@@ -110,3 +110,4 @@ class Loginza(object):
SCRIPT(_src="https://s3-eu-west-1.amazonaws.com/s1.loginza.ru/js/widget.js", _type="text/javascript"))
return form
+17 -16
View File
@@ -6,24 +6,24 @@ from gluon.dal import DAL
def motp_auth(db=DAL('sqlite://storage.sqlite'),
time_offset=60):
"""
motp allows you to login with a one time password(OTP) generated on a motp client,
motp allows you to login with a one time password(OTP) generated on a motp client,
motp clients are available for practically all platforms.
to know more about OTP visit http://en.wikipedia.org/wiki/One-time_password
to know more visit http://motp.sourceforge.net
Written by Madhukar R Pai (madspai@gmail.com)
License : MIT or GPL v2
thanks and credits to the web2py community
to use motp_auth:
motp_auth.py has to be located in gluon/contrib/login_methods/ folder
first auth_user has to have 2 extra fields - motp_secret and motp_pin
for that define auth like shown below:
## after auth = Auth(db)
db.define_table(
auth.settings.table_user_name,
@@ -42,7 +42,7 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
writable=False, readable=False, default=''),
Field('registration_id', length=512, # required
writable=False, readable=False, default=''))
##validators
custom_auth_table = db[auth.settings.table_user_name] # get the custom_auth_table
custom_auth_table.first_name.requires = \
@@ -53,22 +53,22 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
custom_auth_table.email.requires = [
IS_EMAIL(error_message=auth.messages.invalid_email),
IS_NOT_IN_DB(db, custom_auth_table.email)]
auth.settings.table_user = custom_auth_table # tell auth to use custom_auth_table
## before auth.define_tables()
##after that:
##after that:
from gluon.contrib.login_methods.motp_auth import motp_auth
auth.settings.login_methods.append(motp_auth(db=db))
##Instructions for using MOTP
- after configuring motp for web2py, Install a MOTP client on your phone (android,IOS, java, windows phone, etc)
- initialize the motp client (to reset a motp secret type in #**#),
- initialize the motp client (to reset a motp secret type in #**#),
During user creation enter the secret generated during initialization into the motp_secret field in auth_user and
similarly enter a pre-decided pin into the motp_pin
- done.. to login, just generate a fresh OTP by typing in the pin and use the OTP as password
###To Dos###
- both motp_secret and pin are stored in plain text! need to have some way of encrypting
- web2py stores the password in db on successful login (should not happen)
@@ -85,7 +85,7 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
hash = md5(to_hash).hexdigest()[:6]
if otp == hash: return True
return False
def motp_auth_aux(email,
password,
db=db,
@@ -102,3 +102,4 @@ def motp_auth(db=DAL('sqlite://storage.sqlite'),
else: return False
return False
return motp_auth_aux
@@ -188,3 +188,4 @@ class OAuthAccount(object):
@@ -66,7 +66,7 @@ class OAuthAccount(object):
if not self.accessToken():
return None
if not self.graph:
self.graph = GraphAPI((self.accessToken()))
@@ -117,7 +117,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
"""
Build the url opener for managing HTTP Basic Athentication
"""
# Create an OpenerDirector with support
# Create an OpenerDirector with support
# for Basic HTTP Authentication...
auth_handler = urllib2.HTTPBasicAuthHandler()
@@ -172,7 +172,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
if current.session.token.has_key('expires_in'):
exps = 'expires_in'
elif current.session.token.has_key('expires'):
exps = 'expires'
exps = 'expires'
else:
exps = None
current.session.token['expires'] = exps and \
@@ -185,7 +185,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
current.session.token = None
return None
def __init__(self, g=None,
def __init__(self, g=None,
client_id=None, client_secret=None,
auth_url=None, token_url=None, **args):
"""
@@ -215,7 +215,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
def get_user(self):
"""
Override this method by sublcassing the class.
"""
if not current.session.token: return None
return dict(first_name = 'Pinco',
@@ -274,3 +274,4 @@ server for requests. It can be used for the optional"scope" parameters for Face
return current.session.code
return None
@@ -630,3 +630,4 @@ class Web2pyStore(OpenIDStore):
return self.cleanupNonces(), self.cleanupAssociations()
+1
View File
@@ -20,3 +20,4 @@ def pam_auth():
return pam_auth_aux
+4 -3
View File
@@ -53,12 +53,12 @@ class RPXAccount(object):
self.prompt = prompt
self.on_login_failure = on_login_failure
self.mappings = Storage()
dn = {'givenName':'','familyName':''}
self.mappings.Facebook = lambda profile, dn=dn:\
dict(registration_id = profile.get("identifier",""),
username = profile.get("preferredUsername",""),
email = profile.get("email",""),
email = profile.get("email",""),
first_name = profile.get("name",dn).get("givenName",""),
last_name = profile.get("name",dn).get("familyName",""))
self.mappings.Google = lambda profile, dn=dn:\
@@ -120,8 +120,9 @@ def use_janrain(auth,filename='private/janrain.key',**kwargs):
request = current.request
domain,key = open(path,'r').read().strip().split(':')
host = current.request.env.http_host
url = '%s' % URL(r=request, c='default', f='user', args=['login'], scheme=True)
url = URL('default', 'user', args='login', scheme=True)
auth.settings.actions_disabled = \
['register','change_password','request_reset_password']
auth.settings.login_form = RPXAccount(
request, api_key=key,domain=domain, url = url,**kwargs)
+3 -2
View File
@@ -53,7 +53,7 @@ class X509Auth(object):
# cn = self.subject.cn
self.subject = Storage(filter(None,
map(lambda x:
(x,map(lambda y:
(x,map(lambda y:
y.get_data().as_text(),
subject.get_entries_by_nid(subject.nid[x]))),
subject.nid.keys())))
@@ -63,7 +63,7 @@ class X509Auth(object):
def login_form(self, **args):
raise HTTP(403,'Login not allowed. No valid x509 crentials')
def login_url(self, next="/"):
raise HTTP(403,'Login not allowed. No valid x509 crentials')
@@ -102,3 +102,4 @@ class X509Auth(object):
return profile
+8 -8
View File
@@ -523,7 +523,7 @@ regex_list=re.compile('^(?:(#{1,6}|\.+ |\++ |\++\. |\-+ |\-+\. )\s*)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.]+)',re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[^\s\'\"\]\}\)]+)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]')
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$')
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio)(?:\s+(?P<w>\d+px))?\s*$')
@@ -598,7 +598,7 @@ def render(text,
- class_prefix is a prefix for ALL classes in markmin text. E.g. if class_prefix='my_'
then for ``test``:cls class will be changed to "my_cls" (default value is '')
- id_prefix is prefix for ALL ids in markmin text (default value is 'markmin_'). E.g.:
-- [[id]] will be converted to <a name="markmin_id"></a>
-- [[id]] will be converted to <div class="anchor" id="markmin_id"></div>
-- [[link #id]] will be converted to <a href="#markmin_id">link</a>
-- ``test``:cls[id] will be converted to <code class="cls" id="markmin_id">test</code>
@@ -729,19 +729,19 @@ def render(text,
'<p>[[probe]]</p>'
>>> render(r"\\\\[[probe]]")
'<p>\\\\<a name="markmin_probe"></a></p>'
'<p>\\\\<div class="anchor" id="markmin_probe"></div></p>'
>>> render(r"\\\\\\[[probe]]")
'<p>\\\\[[probe]]</p>'
>>> render(r"\\\\\\\\[[probe]]")
'<p>\\\\\\\\<a name="markmin_probe"></a></p>'
'<p>\\\\\\\\<div class="anchor" id="markmin_probe"></div></p>'
>>> render(r"\\\\\\\\\[[probe]]")
'<p>\\\\\\\\[[probe]]</p>'
>>> render(r"\\\\\\\\\\\[[probe]]")
'<p>\\\\\\\\\\\\<a name="markmin_probe"></a></p>'
'<p>\\\\\\\\\\\\<div class="anchor" id="markmin_probe"></div></p>'
>>> render("``[[ [\\[[probe\]\\]] URL\\[x\\]]]``:red[dummy_params]")
'<span style="color: red"><a href="URL[x]" title="[[probe]]">URL[x]</a></span>'
@@ -789,7 +789,7 @@ def render(text,
'<p><strong>test 1</strong></p>'
>>> render('[[id1 [span **messag** in ''markmin''] ]] ... [[**link** to id [link\\\'s title] #mark1]]')
'<p><a name="markmin_id1">span <strong>messag</strong> in markmin</a> ... <a href="#markmin_mark1" title="link\\\'s title"><strong>link</strong> to id</a></p>'
'<p><div class="anchor" id="markmin_id1">span <strong>messag</strong> in markmin</div> ... <a href="#markmin_mark1" title="link\\\'s title"><strong>link</strong> to id</a></p>'
"""
if autolinks=="default": autolinks = autolinks_simple
@@ -801,7 +801,7 @@ def render(text,
# this is experimental @{function/args}
# turns into a digitally signed URL
def u1(match,URL=URL):
a,c,f,args = match.group('a','c','f','args')
a,c,f,args = match.group('a','c','f','args')
return URL(a=a or None,c=c or None,f = f or None,
args=args.split('/'), scheme=True, host=True)
text = regex_URL.sub(u1,text)
@@ -1200,7 +1200,7 @@ def render(text,
protolinks, class_prefix, id_prefix) if t else k
return '<a href="%(k)s"%(title)s%(target)s>%(t)s</a>' \
% dict(k=k, title=title, target=target, t=t)
return '<a name="%s">%s</a>' % (escape(id_prefix+t),
return '<div class="anchor" id="%s">%s</div>' % (escape(id_prefix+t),
render(a, {},{},'br', URL,
environment, latex, autolinks,
protolinks, class_prefix, id_prefix))
+1
View File
@@ -911,3 +911,4 @@ if __name__ == '__main__':
+1
View File
@@ -127,3 +127,4 @@ if __name__ == "__main__":
+1
View File
@@ -129,3 +129,4 @@ def test():
if __name__ == '__main__':
test()
+1
View File
@@ -171,3 +171,4 @@ if __name__ == '__main__':
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"FPDF for python (a.k.a. pyfpdf)"
# Read more about this http://code.google.com/p/pyfpdf
# Please note that new package name is fpdf (to avoid some naming conflicts)
# import fpdf into pyfpdf for backward compatibility (prior web2py 2.0):
from fpdf import *
# import warnings
# warnings.warn("pyfpdf package name is deprecated, please use fpdf instead")
-1
View File
@@ -1 +0,0 @@
Read more about this http://code.google.com/p/pyfpdf
-5
View File
@@ -1,5 +0,0 @@
from fpdf import FPDF
from html import HTMLMixin
from template import Template
-736
View File
@@ -1,736 +0,0 @@
#!/usr/bin/python
# -*- coding: latin-1 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
"Visual Template designer for PyFPDF (using wxPython OGL library)"
__author__ = "Mariano Reingart <reingart@gmail.com>"
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
__license__ = "GPL 3.0"
__version__ = "1.01a"
# Based on:
# * pySjetch.py wxPython sample application
# * OGL.py and other wxPython demo modules
import os, sys
import wx
import wx.lib.ogl as ogl
from wx.lib.wordwrap import wordwrap
DEBUG = True
class CustomDialog(wx.Dialog):
"A dinamyc dialog to ask user about arbitrary fields"
def __init__(
self, parent, ID, title, size=wx.DefaultSize, pos=wx.DefaultPosition,
style=wx.DEFAULT_DIALOG_STYLE, fields=None, data=None,
):
wx.Dialog.__init__ (self, parent, ID, title, pos, size, style)
sizer = wx.BoxSizer(wx.VERTICAL)
self.textctrls = {}
for field in fields:
box = wx.BoxSizer(wx.HORIZONTAL)
label = wx.StaticText(self, -1, field)
label.SetHelpText("This is the help text for the label")
box.Add(label, 1, wx.ALIGN_CENTRE|wx.ALL, 5)
text = wx.TextCtrl(self, -1, "", size=(80,-1))
text.SetHelpText("Here's some help text for field #1")
if field in data:
text.SetValue(repr(data[field]))
box.Add(text, 1, wx.ALIGN_CENTRE|wx.ALL, 1)
sizer.Add(box, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 1)
self.textctrls[field] = text
line = wx.StaticLine(self, -1, size=(20,-1), style=wx.LI_HORIZONTAL)
sizer.Add(line, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.RIGHT|wx.TOP, 5)
btnsizer = wx.StdDialogButtonSizer()
btn = wx.Button(self, wx.ID_OK)
btn.SetHelpText("The OK button completes the dialog")
btn.SetDefault()
btnsizer.AddButton(btn)
btn = wx.Button(self, wx.ID_CANCEL)
btn.SetHelpText("The Cancel button cancels the dialog. (Cool, huh?)")
btnsizer.AddButton(btn)
btnsizer.Realize()
sizer.Add(btnsizer, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.SetSizer(sizer)
sizer.Fit(self)
@classmethod
def do_input(Class, parent, title, fields, data):
dlg = Class(parent, -1, title, size=(350, 200),
style=wx.DEFAULT_DIALOG_STYLE, # & ~wx.CLOSE_BOX,
fields=fields, data=data
)
dlg.CenterOnScreen()
while 1:
val = dlg.ShowModal()
if val == wx.ID_OK:
values = {}
for field in fields:
try:
values[field] = eval(dlg.textctrls[field].GetValue())
except Exception, e:
msg = wx.MessageDialog(parent, unicode(e),
"Error in field %s" % field,
wx.OK | wx.ICON_INFORMATION
)
msg.ShowModal()
msg.Destroy()
break
else:
return dict([(field, values[field]) for field in fields])
else:
return None
class MyEvtHandler(ogl.ShapeEvtHandler):
"Custom Event Handler for Shapes"
def __init__(self, callback):
ogl.ShapeEvtHandler.__init__(self)
self.callback = callback
def OnLeftClick(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
canvas = shape.GetCanvas()
dc = wx.ClientDC(canvas)
canvas.PrepareDC(dc)
if shape.Selected() and keys & ogl.KEY_SHIFT:
shape.Select(False, dc)
#canvas.Redraw(dc)
canvas.Refresh(False)
else:
redraw = False
shapeList = canvas.GetDiagram().GetShapeList()
toUnselect = []
for s in shapeList:
if s.Selected() and not keys & ogl.KEY_SHIFT:
# If we unselect it now then some of the objects in
# shapeList will become invalid (the control points are
# shapes too!) and bad things will happen...
toUnselect.append(s)
shape.Select(True, dc)
if toUnselect:
for s in toUnselect:
s.Select(False, dc)
##canvas.Redraw(dc)
canvas.Refresh(False)
self.callback()
def OnEndDragLeft(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
if not shape.Selected():
self.OnLeftClick(x, y, keys, attachment)
self.callback()
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.callback()
def OnMovePost(self, dc, x, y, oldX, oldY, display):
shape = self.GetShape()
ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
self.callback()
if "wxMac" in wx.PlatformInfo:
shape.GetCanvas().Refresh(False)
def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
self.callback("LeftDoubleClick")
def OnRightClick(self, *dontcare):
self.callback("RightClick")
class Element(object):
"Visual class that represent a placeholder in the template"
fields = ['name', 'type',
'x1', 'y1', 'x2', 'y2',
'font', 'size',
'bold', 'italic', 'underline',
'foreground', 'background',
'align', 'text', 'priority',]
def __init__(self, canvas=None, frame=None, zoom=5.0, static=False, **kwargs):
self.kwargs = kwargs
self.zoom = zoom
self.frame = frame
self.canvas = canvas
self.static = static
name = kwargs['name']
kwargs['type']
type = kwargs['type']
x, y, w, h = self.set_coordinates(kwargs['x1'], kwargs['y1'], kwargs['x2'], kwargs['y2'])
text = kwargs['text']
shape = self.shape = ogl.RectangleShape(w, h)
if not static:
shape.SetDraggable(True, True)
shape.SetX(x)
shape.SetY(y)
#if pen: shape.SetPen(pen)
#if brush: shape.SetBrush(brush)
shape.SetBrush(wx.TRANSPARENT_BRUSH)
if type not in ('L', 'B', 'BC'):
if not static:
pen = wx.LIGHT_GREY_PEN
else:
pen = wx.RED_PEN
shape.SetPen(pen)
self.text = kwargs['text']
evthandler = MyEvtHandler(self.evt_callback)
evthandler.SetShape(shape)
evthandler.SetPreviousHandler(shape.GetEventHandler())
shape.SetEventHandler(evthandler)
shape.SetCentreResize(False)
shape.SetMaintainAspectRatio(False)
canvas.AddShape( shape )
@classmethod
def new(Class, parent):
data = dict(name='some_name', type='T',
x1=5.0, y1=5.0, x2=100.0, y2=10.0,
font="Arial", size=12,
bold=False, italic=False, underline=False,
foreground= 0x000000, background=0xFFFFFF,
align="L", text="", priority=0)
data = CustomDialog.do_input(parent, 'New element', Class.fields, data)
if data:
return Class(canvas=parent.canvas, frame=parent, **data)
def edit(self):
"Edit current element (show a dialog box with all fields)"
data = self.kwargs.copy()
x1, y1, x2, y2 = self.get_coordinates()
data.update(dict(name=self.name,
text=self.text,
x1=x1, y1=y1, x2=x2, y2=y2,
))
data = CustomDialog.do_input(self.frame, 'Edit element', self.fields, data)
if data:
self.kwargs.update(data)
self.name = data['name']
self.text = data['text']
x,y, w, h = self.set_coordinates(data['x1'], data['y1'], data['x2'], data['y2'])
self.shape.SetX(x)
self.shape.SetY(y)
self.shape.SetWidth(w)
self.shape.SetHeight(h)
self.canvas.Refresh(False)
self.canvas.GetDiagram().ShowAll(1)
def edit_text(self):
"Allow text edition (i.e. for doubleclick)"
dlg = wx.TextEntryDialog(
self.frame, 'Text for %s' % self.name,
'Edit Text', '')
if self.text:
dlg.SetValue(self.text)
if dlg.ShowModal() == wx.ID_OK:
self.text = dlg.GetValue().encode("latin1")
dlg.Destroy()
def copy(self):
"Return an identical duplicate"
kwargs = self.as_dict()
element = Element(canvas=self.canvas, frame=self.frame, zoom=self.zoom, static=self.static, **kwargs)
return element
def remove(self):
"Erases visual shape from OGL canvas (element must be deleted manually)"
self.canvas.RemoveShape(self.shape)
def move(self, dx, dy):
"Change pdf coordinates (converting to wx internal values)"
x1, y1, x2, y2 = self.get_coordinates()
x1 += dx
x2 += dx
y1 += dy
y2 += dy
x, y, w, h = self.set_coordinates(x1, y1, x2, y2)
self.shape.SetX(x)
self.shape.SetY(y)
def evt_callback(self, evt_type=None):
"Event dispatcher"
if evt_type=="LeftDoubleClick":
self.edit_text()
if evt_type=='RightClick':
self.edit()
# update the status bar
x1, y1, x2, y2 = self.get_coordinates()
self.frame.SetStatusText("%s (%0.2f, %0.2f) - (%0.2f, %0.2f)" %
(self.name, x1, y1, x2, y2))
def get_coordinates(self):
"Convert from wx to pdf coordinates"
x, y = self.shape.GetX(), self.shape.GetY()
w, h = self.shape.GetBoundingBoxMax()
w -= 1
h -= 1
x1 = x/self.zoom - w/self.zoom/2.0
x2 = x/self.zoom + w/self.zoom/2.0
y1 = y/self.zoom - h/self.zoom/2.0
y2 = y/self.zoom + h/self.zoom/2.0
return x1, y1, x2, y2
def set_coordinates(self, x1, y1, x2, y2):
"Convert from pdf to wx coordinates"
x1 = x1 * self.zoom
x2 = x2 * self.zoom
y1 = y1 * self.zoom
y2 = y2 * self.zoom
# shapes seems to be centred, pdf coord not
w = max(x1, x2) - min(x1, x2) + 1
h = max(y1, y2) - min(y1, y2) + 1
x = (min(x1, x2) + w/2.0)
y = (min(y1, y2) + h/2.0)
return x, y, w, h
def text(self, txt=None):
if txt is not None:
if not isinstance(txt,str):
txt = str(txt)
self.kwargs['text'] = txt
self.shape.ClearText()
for line in txt.split('\n'):
self.shape.AddText(unicode(line, "latin1"))
self.canvas.Refresh(False)
return self.kwargs['text']
text = property(text, text)
def set_x(self, x):
self.shape.SetX(x)
self.canvas.Refresh(False)
self.evt_callback()
def set_y(self, y):
self.shape.SetY(y)
self.canvas.Refresh(False)
self.evt_callback()
def get_x(self):
return self.shape.GetX()
def get_y(self):
return self.shape.GetY()
x = property(get_x, set_x)
y = property(get_y, set_y)
def selected(self, sel=None):
if sel is not None:
print "Setting Select(%s)" % sel
self.shape.Select(sel)
return self.shape.Selected()
selected = property(selected, selected)
def name(self, name=None):
if name is not None:
self.kwargs['name'] = name
return self.kwargs['name']
name = property(name, name)
def __contains__(self, k):
"Implement in keyword for searchs"
return k in self.name.lower() or self.text and k in self.text.lower()
def as_dict(self):
"Return a dictionary representation, used by pyfpdf"
d = self.kwargs
x1, y1, x2, y2 = self.get_coordinates()
d.update({
'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2,
'text': self.text})
return d
class AppFrame(wx.Frame):
"OGL Designer main window"
title = "PyFPDF Template Designer (wx OGL)"
def __init__(self):
wx.Frame.__init__( self,
None, -1, self.title,
size=(640,480),
style=wx.DEFAULT_FRAME_STYLE )
sys.excepthook = self.except_hook
self.filename = ""
# Create a toolbar:
tsize = (16,16)
self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
artBmp = wx.ArtProvider.GetBitmap
self.toolbar.AddSimpleTool(
wx.ID_NEW, artBmp(wx.ART_NEW, wx.ART_TOOLBAR, tsize), "New")
self.toolbar.AddSimpleTool(
wx.ID_OPEN, artBmp(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize), "Open")
self.toolbar.AddSimpleTool(
wx.ID_SAVE, artBmp(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize), "Save")
self.toolbar.AddSimpleTool(
wx.ID_SAVEAS, artBmp(wx.ART_FILE_SAVE_AS, wx.ART_TOOLBAR, tsize),
"Save As...")
#-------
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_UNDO, artBmp(wx.ART_UNDO, wx.ART_TOOLBAR, tsize), "Undo")
self.toolbar.AddSimpleTool(
wx.ID_REDO, artBmp(wx.ART_REDO, wx.ART_TOOLBAR, tsize), "Redo")
self.toolbar.AddSeparator()
#-------
self.toolbar.AddSimpleTool(
wx.ID_CUT, artBmp(wx.ART_CUT, wx.ART_TOOLBAR, tsize), "Remove")
self.toolbar.AddSimpleTool(
wx.ID_COPY, artBmp(wx.ART_COPY, wx.ART_TOOLBAR, tsize), "Duplicate")
self.toolbar.AddSimpleTool(
wx.ID_PASTE, artBmp(wx.ART_PASTE, wx.ART_TOOLBAR, tsize), "Insert")
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_FIND, artBmp(wx.ART_FIND, wx.ART_TOOLBAR, tsize), "Find")
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(
wx.ID_PRINT, artBmp(wx.ART_PRINT, wx.ART_TOOLBAR, tsize), "Print")
self.toolbar.AddSimpleTool(
wx.ID_ABOUT, artBmp(wx.ART_HELP, wx.ART_TOOLBAR, tsize), "About")
self.toolbar.Realize()
self.toolbar.EnableTool(wx.ID_SAVEAS, False)
self.toolbar.EnableTool(wx.ID_UNDO, False)
self.toolbar.EnableTool(wx.ID_REDO, False)
menu_handlers = [
(wx.ID_NEW, self.do_new),
(wx.ID_OPEN, self.do_open),
(wx.ID_SAVE, self.do_save),
(wx.ID_PRINT, self.do_print),
(wx.ID_FIND, self.do_find),
(wx.ID_CUT, self.do_cut),
(wx.ID_COPY, self.do_copy),
(wx.ID_PASTE, self.do_paste),
(wx.ID_ABOUT, self.do_about),
]
for menu_id, handler in menu_handlers:
self.Bind(wx.EVT_MENU, handler, id = menu_id)
sizer = wx.BoxSizer(wx.VERTICAL)
# put stuff into sizer
self.CreateStatusBar()
canvas = self.canvas = ogl.ShapeCanvas( self )
maxWidth = 1500
maxHeight = 2000
canvas.SetScrollbars(20, 20, maxWidth/20, maxHeight/20)
sizer.Add( canvas, 1, wx.GROW )
canvas.SetBackgroundColour("WHITE") #
diagram = self.diagram = ogl.Diagram()
canvas.SetDiagram( diagram )
diagram.SetCanvas( canvas )
diagram.SetSnapToGrid( False )
# apply sizer
self.SetSizer(sizer)
self.SetAutoLayout(1)
self.Show(1)
self.Bind(wx.EVT_CHAR_HOOK, self.on_key_event)
self.elements = []
def on_key_event(self, event):
""" Respond to a keypress event.
We make the arrow keys move the selected object(s) by one pixel in
the given direction.
"""
step = 1
if event.ControlDown():
step = 20
if event.GetKeyCode() == wx.WXK_UP:
self.move_elements(0, -step)
elif event.GetKeyCode() == wx.WXK_DOWN:
self.move_elements(0, step)
elif event.GetKeyCode() == wx.WXK_LEFT:
self.move_elements(-step, 0)
elif event.GetKeyCode() == wx.WXK_RIGHT:
self.move_elements(step, 0)
elif event.GetKeyCode() == wx.WXK_DELETE:
self.do_cut()
else:
event.Skip()
def do_new(self, evt=None):
for element in self.elements:
element.remove()
self.elements = []
# draw paper size guides
for k, (w, h) in [('legal', (216, 356)), ('A4', (210, 297)), ('letter', (216, 279))]:
self.create_elements(
k, 'R', 0, 0, w, h,
size=70, foreground=0x808080, priority=-100,
canvas=self.canvas, frame=self, static=True)
self.diagram.ShowAll( 1 )
def do_open(self, evt):
dlg = wx.FileDialog(
self, message="Choose a file",
defaultDir=os.getcwd(),
defaultFile="invoice.csv",
wildcard="CSV Files (*.csv)|*.csv",
style=wx.OPEN
)
if dlg.ShowModal() == wx.ID_OK:
# This returns a Python list of files that were selected.
self.filename = dlg.GetPaths()[0]
dlg.Destroy()
self.SetTitle(self.filename + " - " + self.title)
self.do_new()
tmp = []
f = open(self.filename)
try:
filedata = f.readlines()
finally:
f.close()
for lno, linea in enumerate(filedata):
if DEBUG: print "processing line", lno, linea
args = []
for i,v in enumerate(linea.split(";")):
if not v.startswith("'"):
v = v.replace(",",".")
else:
v = v#.decode('latin1')
if v.strip()=='':
v = None
else:
v = eval(v.strip())
args.append(v)
tmp.append(args)
# sort by z-order (priority)
for args in sorted(tmp, key=lambda t: t[-1]):
if DEBUG: print args
self.create_elements(*args)
self.diagram.ShowAll( 1 ) #
return True
def do_save(self, evt, filename=None):
try:
from time import gmtime, strftime
ts = strftime("%Y%m%d%H%M%S", gmtime())
os.rename(self.filename, self.filename + ts + ".bak")
except Exception, e:
if DEBUG: print e
pass
def csv_repr(v, decimal_sep="."):
if isinstance(v, float):
return ("%0.2f" % v).replace(".", decimal_sep)
else:
return repr(v)
f = open(self.filename, "w")
try:
for element in sorted(self.elements, key=lambda e:e.name):
if element.static:
continue
d = element.as_dict()
l = [d['name'], d['type'],
d['x1'], d['y1'], d['x2'], d['y2'],
d['font'], d['size'],
d['bold'], d['italic'], d['underline'],
d['foreground'], d['background'],
d['align'], d['text'], d['priority'],
]
f.write(";".join([csv_repr(v) for v in l]))
f.write("\n")
finally:
f.close()
def do_print(self, evt):
# genero el renderizador con propiedades del PDF
from template import Template
t = Template(elements=[e.as_dict() for e in self.elements if not e.static])
t.add_page()
if not t['logo'] or not os.path.exists(t['logo']):
# put a default logo so it doesn't trow an exception
logo = os.path.join(os.path.dirname(__file__), 'tutorial','logo.png')
t.set('logo', logo)
try:
t.render(self.filename +".pdf")
except:
if DEBUG and False:
import pdb;
pdb.pm()
else:
raise
if sys.platform=="linux2":
os.system("evince ""%s""" % self.filename +".pdf")
else:
os.startfile(self.filename +".pdf")
def do_find(self, evt):
# busco nombre o texto
dlg = wx.TextEntryDialog(
self, 'Enter text to search for',
'Find Text', '')
if dlg.ShowModal() == wx.ID_OK:
txt = dlg.GetValue().encode("latin1").lower()
for element in self.elements:
if txt in element:
element.selected = True
print "Found:", element.name
self.canvas.Refresh(False)
dlg.Destroy()
def do_cut(self, evt=None):
"Delete selected elements"
new_elements = []
for element in self.elements:
if element.selected:
print "Erasing:", element.name
element.selected = False
self.canvas.Refresh(False)
element.remove()
else:
new_elements.append(element)
self.elements = new_elements
self.canvas.Refresh(False)
self.diagram.ShowAll( 1 )
def do_copy(self, evt):
"Duplicate selected elements"
fields = ['qty', 'dx', 'dy']
data = {'qty': 1, 'dx': 0.0, 'dy': 5.0}
data = CustomDialog.do_input(self, 'Copy elements', fields, data)
if data:
new_elements = []
for i in range(1, data['qty']+1):
for element in self.elements:
if element.selected:
print "Copying:", element.name
new_element = element.copy()
name = new_element.name
if len(name)>2 and name[-2:].isdigit():
new_element.name = name[:-2] + "%02d" % (int(name[-2:])+i)
else:
new_element.name = new_element.name + "_copy"
new_element.selected = False
new_element.move(data['dx']*i, data['dy']*i)
new_elements.append(new_element)
self.elements.extend(new_elements)
self.canvas.Refresh(False)
self.diagram.ShowAll( 1 )
def do_paste(self, evt):
"Insert new elements"
element = Element.new(self)
if element:
self.canvas.Refresh(False)
self.elements.append(element)
self.diagram.ShowAll( 1 )
def create_elements(self, name, type, x1, y1, x2, y2,
font="Arial", size=12,
bold=False, italic=False, underline=False,
foreground= 0x000000, background=0xFFFFFF,
align="L", text="", priority=0, canvas=None, frame=None, static=False,
**kwargs):
element = Element(name=name, type=type, x1=x1, y1=y1, x2=x2, y2=y2,
font=font, size=size,
bold=bold, italic=italic, underline=underline,
foreground= foreground, background=background,
align=align, text=text, priority=priority,
canvas=canvas or self.canvas, frame=frame or self,
static=static)
self.elements.append(element)
def move_elements(self, x, y):
for element in self.elements:
if element.selected:
print "moving", element.name, x, y
element.x = element.x + x
element.y = element.y + y
def do_about(self, evt):
info = wx.AboutDialogInfo()
info.Name = self.title
info.Version = __version__
info.Copyright = __copyright__
info.Description = (
"Visual Template designer for PyFPDF (using wxPython OGL library)\n"
"Input files are CSV format describing the layout, separated by ;\n"
"Use toolbar buttons to open, save, print (preview) your template, "
"and there are buttons to find, add, remove or duplicate elements.\n"
"Over an element, a double left click opens edit text dialog, "
"and a right click opens edit properties dialog. \n"
"Multiple element can be selected with shift left click. \n"
"Use arrow keys or drag-and-drop to move elements.\n"
"For further information see project webpage:"
)
info.WebSite = ("http://code.google.com/p/pyfpdf/wiki/Templates",
"pyfpdf Google Code Project")
info.Developers = [ __author__, ]
info.License = wordwrap(__license__, 500, wx.ClientDC(self))
# Then we call wx.AboutBox giving it that info object
wx.AboutBox(info)
def except_hook(self, type, value, trace):
import traceback
exc = traceback.format_exception(type, value, trace)
for e in exc: wx.LogError(e)
wx.LogError('Unhandled Error: %s: %s'%(str(type), str(value)))
app = wx.PySimpleApp()
ogl.OGLInitialize()
frame = AppFrame()
app.MainLoop()
app.Destroy()
+1
View File
@@ -964,3 +964,4 @@ if __name__ == '__main__':
+1
View File
@@ -167,3 +167,4 @@ class RedisClient(object):
+1
View File
@@ -592,3 +592,4 @@ if __name__ == '__main__':
+1
View File
@@ -270,3 +270,4 @@ if __name__=='__main__':
+1
View File
@@ -151,3 +151,4 @@ if __name__ == "__main__":
+1
View File
@@ -116,3 +116,4 @@ def sms_email(number,provider):
+1
View File
@@ -879,3 +879,4 @@ if __name__ == '__main__':
print s['c'].computed_value
+1
View File
@@ -66,3 +66,4 @@ if __name__=='__main__':
+1
View File
@@ -247,3 +247,4 @@ class TaskBarIcon:
+1
View File
@@ -95,3 +95,4 @@ if __name__=='__main__':
print(t.getReportText(orderByCost=False))
+1
View File
@@ -520,3 +520,4 @@ class mobilize(object):
+1
View File
@@ -329,3 +329,4 @@ class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
+83 -38
View File
@@ -174,7 +174,7 @@ import platform
CALLABLETYPES = (types.LambdaType, types.FunctionType,
types.BuiltinFunctionType,
types.MethodType, types.BuiltinMethodType)
TABLE_ARGS = ('migrate','primarykey','fake_migrate','format','singular','plural','trigger_name','sequence_name','common_filter','polymodel','table_class')
TABLE_ARGS = ('migrate','primarykey','fake_migrate','format','singular','plural','trigger_name','sequence_name','common_filter','polymodel','table_class','on_define')
###################################################################################
# following checks allow the use of dal without web2py, as a standalone module
@@ -1317,7 +1317,7 @@ class BaseAdapter(ConnectionPool):
tablename,fieldname = item.split('.')
new_fields.append(self.db[tablename][fieldname])
else:
new_fields.append(Expression(self.db,item))
new_fields.append(Expression(self.db,lambda:item))
else:
new_fields.append(item)
# ## if no fields specified take them all from the requested tables
@@ -2735,6 +2735,14 @@ class OracleAdapter(BaseAdapter):
self.execute('SELECT %s.currval FROM dual;' % sequence_name)
return int(self.cursor.fetchone()[0])
def parse_value(self, value, field_type, blob_decode=True):
if blob_decode and isinstance(value, cx_Oracle.LOB):
try:
value = value.read()
except cx_Oracle.ProgrammingError:
# After a subsequent fetch the LOB value is not valid anymore
pass
return BaseAdapter.parse_value(self, value, field_type, blob_decode)
class MSSQLAdapter(BaseAdapter):
@@ -6281,11 +6289,9 @@ class Row(dict):
this is only used to store a Row
"""
def __getattr__(self, key):
return self[key]
def __setattr__(self, key, value):
self[key] = value
__setattr__ = dict.__setitem__
__getattr__ = dict.__getitem__
__delattr__ = dict.__delitem__
def __getitem__(self, key):
key=str(key)
@@ -6299,8 +6305,10 @@ class Row(dict):
key = m.group(2)
return dict.__getitem__(self, key)
def __call__(self,key):
return self.__getitem__(key)
__call__ = __getitem__
#def __call__(self,key):
# return self.__getitem__(key)
def __setitem__(self, key, value):
dict.__setitem__(self, str(key), value)
@@ -6924,7 +6932,7 @@ def index():
tablename,
*fields,
**args
):
):
if not isinstance(tablename,str):
raise SyntaxError, "missing table name"
elif tablename.startswith('_') or hasattr(self,tablename) or \
@@ -6938,7 +6946,7 @@ def index():
invalid_args = [key for key in args if not key in TABLE_ARGS]
if invalid_args:
raise SyntaxError, 'invalid table "%s" attributes: %s' \
% (tablename,invalid_args)
% (tablename,invalid_args)
if self._lazy_tables and not tablename in self._LAZY_TABLES:
self._LAZY_TABLES[tablename] = (tablename,fields,args)
table = None
@@ -6977,6 +6985,8 @@ def index():
sql_locker.release()
else:
table._dbt = None
on_define = args.get('on_define',None)
if on_define: on_define(table)
return table
def __iter__(self):
@@ -7025,9 +7035,10 @@ def index():
thread.instances.remove(self._adapter)
self._adapter.close()
def executesql(self, query, placeholders=None, as_dict=False):
def executesql(self, query, placeholders=None, as_dict=False,
fields=None, colnames=None):
"""
placeholders is optional and will always be None when using DAL.
placeholders is optional and will always be None.
If using raw SQL with placeholders, placeholders may be
a sequence of values to be substituted in
or, (if supported by the DB driver), a dictionary with keys
@@ -7044,7 +7055,21 @@ def index():
[{field1: value1, field2: value2}, {field1: value1b, field2: value2b}]
--bmeredyk
Added 2012-08-24 "fields" optional argument. If not None, the
results cursor returned by the DB driver will be converted to a
DAL Rows object using the db._adapter.parse() method. This requires
specifying the "fields" argument as a list of DAL Field objects
that match the fields returned from the DB. The Field objects should
be part of one or more Table objects defined on the DAL object.
The "fields" list can include one or more DAL Table objects in addition
to or instead of including Field objects, or it can be just a single
table (not in a list). In that case, the Field objects will be
extracted from the table(s).
The field names will be extracted from the Field objects, or optionally,
a list of field names can be provided (in tablename.fieldname format)
via the "colnames" argument. Note, the fields and colnames must be in
the same order as the fields in the results cursor returned from the DB.
"""
if placeholders:
self._adapter.execute(query, placeholders)
@@ -7064,11 +7089,22 @@ def index():
# convert the list for each row into a dictionary so it's
# easier to work with. row['field_name'] rather than row[0]
return [dict(zip(fields,row)) for row in data]
# see if any results returned from database
try:
return self._adapter.cursor.fetchall()
except:
return None
data = self._adapter.cursor.fetchall()
if fields:
if not isinstance(fields, list):
fields = [fields]
extracted_fields = []
for field in fields:
if isinstance(field, Table):
extracted_fields.extend([f for f in field])
else:
extracted_fields.append(field)
if not colnames:
colnames = ['%s.%s' % (f.tablename, f.name)
for f in extracted_fields]
data = self._adapter.parse(
data, fields=extracted_fields, colnames=colnames)
return data
def _update_referenced_by(self, other):
for tablename in self.tables:
@@ -7462,15 +7498,15 @@ class Table(dict):
'value must be a dictionary: %s' % value
dict.__setitem__(self, str(key), value)
def __getattr__(self, key):
return self[key]
__getattr__ = __getitem__
def __delitem__(self, key):
if isinstance(key, dict):
query = self._build_query(key)
if not self._db(query).delete():
raise SyntaxError, 'No such record: %s' % key
elif not str(key).isdigit() or not self._db(self._id == key).delete():
elif not str(key).isdigit() or \
not self._db(self._id == key).delete():
raise SyntaxError, 'No such record: %s' % key
def __setattr__(self, key, value):
@@ -7499,33 +7535,38 @@ class Table(dict):
return self._db._adapter.drop(self,mode)
def _listify(self,fields,update=False):
new_fields = []
new_fields_names = []
new_fields = {} # format: new_fields[name] = (field,value)
# store all fields passed as input in new_fields
for name in fields:
if not name in self.fields:
if name != 'id':
raise SyntaxError, 'Field %s does not belong to the table' % name
raise SyntaxError, \
'Field %s does not belong to the table' % name
else:
field = self[name]
value = fields[name]
if field.filter_in: value = field.filter_in(value)
new_fields.append((field,value))
new_fields_names.append(name)
for ofield in self:
if not ofield.name in new_fields_names:
if not update and not ofield.default is None:
new_fields.append((ofield,ofield.default))
elif update and not ofield.update is None:
new_fields.append((ofield,ofield.update))
if field.filter_in:
value = field.filter_in(value)
new_fields[name] = (field,value)
# check all fields that should be in the self table
for ofield in self:
name = ofield.name
# if field is supposed to be computed, compute it!
if ofield.compute:
try:
new_fields.append((ofield,ofield.compute(Row(fields))))
new_fields[name] = (ofield,ofield.compute(Row(fields)))
except KeyError:
pass
if not update and ofield.required and not ofield.name in new_fields_names:
raise SyntaxError,'Table: missing required field: %s' % ofield.name
return new_fields
# if field is required, check its default value
elif not name in new_fields:
if not update and not ofield.default is None:
new_fields[name] = (ofield,ofield.default)
elif update and not ofield.update is None:
new_fields[name] = (ofield,ofield.update)
# error if field if required, record to be create and field missing
if not update and ofield.required and not name in new_fields:
raise SyntaxError, 'Table: missing required field: %s' % name
return new_fields.values()
def _attempt_upload(self, fields):
for field in self:
@@ -8149,6 +8190,9 @@ class Field(Expression):
self.label = label if label!=None else fieldname.replace('_',' ').title()
self.requires = requires if requires!=None else []
def set_attributes(self,*args,**attributes):
self.__dict__.update(*args,**attributes)
def clone(self,point_self_references_to=False,**args):
field = copy.copy(self)
if point_self_references_to and \
@@ -9109,3 +9153,4 @@ if __name__ == '__main__':
+1
View File
@@ -192,3 +192,4 @@ gluon.main.global_settings.debugging = True
+1
View File
@@ -78,3 +78,4 @@ def decoder(buffer):
+1
View File
@@ -397,3 +397,4 @@ def abspath(*relpath, **base):
return os.path.join(global_settings.applications_parent, path)
+2 -1
View File
@@ -101,7 +101,7 @@ class Request(Storage):
self.is_https = False
self.is_local = False
self.global_settings = settings.global_settings
def compute_uuid(self):
self.uuid = '%s/%s.%s.%s' % (
self.application,
@@ -691,3 +691,4 @@ class Session(Storage):
+1
View File
@@ -348,3 +348,4 @@ if __name__ == '__main__':
+96 -29
View File
@@ -484,6 +484,19 @@ class XmlComponent(object):
components += [other]
return CAT(*components)
def add_class(self, name):
""" add a class to _class attribute """
classes = set(self['_class'].split())|set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
def remove_class(self, name):
""" remove a class from _class attribute """
classes = set(self['_class'].split())-set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
class XML(XmlComponent):
"""
use it to wrap a string that contains XML/HTML so that it will not be
@@ -949,6 +962,48 @@ class DIV(XmlComponent):
>>> for c in a.elements('input, select, textarea'): c['_disabled'] = 'disabled'
>>> a.xml()
'<form action="" enctype="multipart/form-data" method="post"><input disabled="disabled" type="text" /><select disabled="disabled"><option value="0">0</option></select><textarea cols="40" disabled="disabled" rows="10"></textarea></form>'
Elements that are matched can also be replaced or removed by specifying
a "replace" argument (note, a list of the original matching elements
is still returned as usual).
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.abc', replace=P('x', _class='xyz'))
>>> print a
<div><div><p class="xyz">x</p><div><p class="xyz">x</p><p class="xyz">x</p></div></div></div>
"replace" can be a callable, which will be passed the original element and
should return a new element to replace it.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.abc', replace=lambda el: P(el[0], _class='xyz'))
>>> print a
<div><div><p class="xyz">x</p><div><p class="xyz">y</p><p class="xyz">z</p></div></div></div>
If replace=None, matching elements will be removed completely.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements('span', find='y', replace=None)
>>> print a
<div><div><span class="abc">x</span><div><span class="abc">z</span></div></div></div>
If a "find_text" argument is specified, elements will be searched for text
components that match find_text, and any matching text components will be
replaced (find_text is ignored if "replace" is not also specified).
Like the "find" argument, "find_text" can be a string or a compiled regex.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='abc'), SPAN('z', _class='abc'))))
>>> b = a.elements(find_text=re.compile('x|y|z'), replace='hello')
>>> print a
<div><div><span class="abc">hello</span><div><span class="abc">hello</span><span class="abc">hello</span></div></div></div>
If other attributes are specified along with find_text, then only components
that match the specified attributes will be searched for find_text.
>>> a = DIV(DIV(SPAN('x', _class='abc'), DIV(SPAN('y', _class='efg'), SPAN('z', _class='abc'))))
>>> b = a.elements('span.efg', find_text=re.compile('x|y|z'), replace='hello')
>>> print a
<div><div><span class="abc">x</span><div><span class="efg">hello</span><span class="abc">z</span></div></div></div>
"""
if len(args)==1:
args = [a.strip() for a in args[0].split(',')]
@@ -977,49 +1032,60 @@ class DIV(XmlComponent):
return self.elements(*args,**kargs)
# make a copy of the components
matches = []
first_only = False
if kargs.has_key("first_only"):
first_only = kargs["first_only"]
del kargs["first_only"]
# check if the component has an attribute with the same
# value as provided
check = True
tag = getattr(self,'tag').replace("/","")
tag = getattr(self,'tag').replace('/', '')
if args and tag not in args:
check = False
for (key, value) in kargs.items():
if isinstance(value,(str,int)):
if self[key] != str(value):
if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)):
if self[key] != str(value):
check = False
elif key in self.attributes:
if not value.search(str(self[key])):
check = False
else:
check = False
elif key in self.attributes:
if not value.search(str(self[key])):
check = False
else:
check = False
if 'find' in kargs:
find = kargs['find']
is_regex = not isinstance(find, (str, int))
for c in self.components:
if isinstance(find,(str,int)):
if isinstance(c,str) and str(find) in c:
check = True
else:
if isinstance(c,str) and find.search(c):
check = True
if (isinstance(c, str) and ((is_regex and find.search(c)) or
(str(find) in c))):
check = True
# if found, return the component
if check:
matches.append(self)
if first_only:
return matches
# loop the copy
for c in self.components:
if isinstance(c, XmlComponent):
kargs['first_only'] = first_only
child_matches = c.elements( *args, **kargs )
if first_only and len(child_matches) != 0:
return child_matches
matches.extend( child_matches )
return matches
first_only = kargs.get('first_only', False)
replace = kargs.get('replace', False)
find_text = replace is not False and kargs.get('find_text', False)
is_regex = not isinstance(find_text, (str, int, bool))
find_components = not (check and first_only)
def replace_component(i):
if replace is None:
del self[i]
elif callable(replace):
self[i] = replace(self[i])
else:
self[i] = replace
# loop the components
if find_text or find_components:
for i, c in enumerate(self.components):
if check and find_text and isinstance(c, str) and \
((is_regex and find_text.search(c)) or (str(find_text) in c)):
replace_component(i)
if find_components and isinstance(c, XmlComponent):
child_matches = c.elements(*args, **kargs)
if len(child_matches):
if not find_text and replace is not False and child_matches[0] is c:
replace_component(i)
if first_only:
return child_matches
matches.extend(child_matches)
return matches
def element(self, *args, **kargs):
"""
@@ -2437,3 +2503,4 @@ if __name__ == '__main__':
+1
View File
@@ -142,3 +142,4 @@ def redirect(location, how=303, client_side=False):
+1
View File
@@ -113,3 +113,4 @@ for module in base_modules + contributed_modules:
+1
View File
@@ -939,3 +939,4 @@ if __name__ == '__main__':
doctest.testmod()
+1
View File
@@ -854,3 +854,4 @@ class HttpServer(object):
+1
View File
@@ -14,3 +14,4 @@ class MessageBoxHandler(logging.Handler):
msg = self.format(record)
tkMessageBox.showinfo('info1', msg)
+1
View File
@@ -32,3 +32,4 @@ regex_extend = re.compile(\
+1
View File
@@ -343,3 +343,4 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
+1
View File
@@ -151,3 +151,4 @@ if __name__=='__main__':

Some files were not shown because too many files have changed in this diff Show More