Merge github.com:web2py/web2py
Conflicts: gluon/contrib/login_methods/rpx_account.py
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 @@
|
||||
Version 2.00.0 (2012-08-22 19:47:08) dev
|
||||
Version 2.00.0 (2012-08-25 16:19:46) dev
|
||||
|
||||
@@ -6,3 +6,5 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -309,3 +309,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,3 +6,4 @@ def webapp_add_wsgi_middleware(app):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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/]]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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}}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -65,3 +65,4 @@ wsgiref.handlers.CGIHandler().run(gluon.main.wsgibase)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,3 +55,4 @@ fcgi.WSGIServer(application, bindAddress='/tmp/fcgi.sock').run()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -103,3 +103,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -52,3 +52,4 @@ if 0:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+25
-9
@@ -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):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -498,3 +498,4 @@ def lazy_cache(key=None,time_expire=None,cache_model='ram'):
|
||||
return decorator
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -56,3 +56,4 @@ def getcfs(key, filename, filter=None):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -691,3 +691,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -723,3 +723,4 @@ def contenttype(filename, default='text/plain'):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -263,3 +263,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -243,3 +243,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -501,3 +501,4 @@ aes_Rcon = array('B',
|
||||
'61c29f254a943366cc831d3a74e8cb'.decode('hex')
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -194,3 +194,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3910,3 +3910,4 @@ def parse(url_file_stream_or_string, etag=None, modified=None, agent=None, refer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
Executable → Regular
+52
-28
@@ -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
@@ -55,3 +55,4 @@ class MemcacheClient(Client):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -90,3 +90,4 @@ def autoretry_datastore_timeouts(attempts=5.0, interval=0.1, exponent=2.0):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,3 +19,4 @@ def button(merchant_id="123456789012345",
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ from gluon.dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, driv
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -58,3 +58,4 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
|
||||
else:
|
||||
return image
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,3 +20,4 @@ def pam_auth():
|
||||
|
||||
return pam_auth_aux
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -911,3 +911,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -127,3 +127,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -129,3 +129,4 @@ def test():
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
||||
|
||||
|
||||
@@ -171,3 +171,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
Read more about this http://code.google.com/p/pyfpdf
|
||||
@@ -1,5 +0,0 @@
|
||||
from fpdf import FPDF
|
||||
from html import HTMLMixin
|
||||
from template import Template
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -964,3 +964,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -167,3 +167,4 @@ class RedisClient(object):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -592,3 +592,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -270,3 +270,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -151,3 +151,4 @@ if __name__ == "__main__":
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -116,3 +116,4 @@ def sms_email(number,provider):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -879,3 +879,4 @@ if __name__ == '__main__':
|
||||
print s['c'].computed_value
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -66,3 +66,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -247,3 +247,4 @@ class TaskBarIcon:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -95,3 +95,4 @@ if __name__=='__main__':
|
||||
print(t.getReportText(orderByCost=False))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -520,3 +520,4 @@ class mobilize(object):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -329,3 +329,4 @@ class _Web2pyDateTrackerImporter(_Web2pyImporter, _DateTrackerImporter):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+83
-38
@@ -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__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -192,3 +192,4 @@ gluon.main.global_settings.debugging = True
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -78,3 +78,4 @@ def decoder(buffer):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -397,3 +397,4 @@ def abspath(*relpath, **base):
|
||||
return os.path.join(global_settings.applications_parent, path)
|
||||
|
||||
|
||||
|
||||
|
||||
+2
-1
@@ -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):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -348,3 +348,4 @@ if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+96
-29
@@ -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__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -142,3 +142,4 @@ def redirect(location, how=303, client_side=False):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -113,3 +113,4 @@ for module in base_modules + contributed_modules:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -939,3 +939,4 @@ if __name__ == '__main__':
|
||||
doctest.testmod()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -854,3 +854,4 @@ class HttpServer(object):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,3 +14,4 @@ class MessageBoxHandler(logging.Handler):
|
||||
msg = self.format(record)
|
||||
tkMessageBox.showinfo('info1', msg)
|
||||
|
||||
|
||||
|
||||
@@ -32,3 +32,4 @@ regex_extend = re.compile(\
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -343,3 +343,4 @@ def crondance(applications_parent, ctype='soft', startup=False, apps=None):
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -151,3 +151,4 @@ if __name__=='__main__':
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user