Compare commits

...

204 Commits

Author SHA1 Message Date
mdipierro a0bbd7885a Merge branch 'master' of github.com:web2py/web2py 2016-03-26 17:56:23 -05:00
mdipierro c2ce90a1fe R-2.14.3 2016-03-26 17:55:52 -05:00
mdipierro 197b018534 fixed stupid.css again 2016-03-26 17:54:11 -05:00
mdipierro 060aeff867 Merge pull request #1251 from BuhtigithuB/add-even-more-test-over-html-py
More coverage and some PEP8 enhencement to html.py
2016-03-26 00:00:43 -05:00
mdipierro d4572c5f38 Merge branch 'master' of github.com:web2py/web2py 2016-03-25 23:57:24 -05:00
mdipierro 46dc83a1bb synced to latest stupid.css 2016-03-25 23:57:13 -05:00
Hardirc f414356b67 More coverage and some PEP8 enhencement to html.py 2016-03-26 00:44:28 -04:00
mdipierro 460a017bab Merge pull request #1250 from Pierre-Thibault/strange_lines
Nonsense sys.exc_info parameter of traceback.format_exc calls.
2016-03-25 18:07:25 -05:00
mdipierro 0b966d7c0a Merge pull request #1249 from leonelcamara/patch-1
imageutils.py missing comma bug
2016-03-25 18:06:56 -05:00
Pierre Thibault d2d16d4081 I was forgetting line 200. 2016-03-25 15:54:01 -04:00
Pierre Thibault ebcf6e5671 Nonsense sys.exc_info parameter of traceback.format_exc calls. 2016-03-25 15:48:58 -04:00
Leonel Câmara 778cc3902b Update imageutils.py
missing comma
2016-03-25 12:45:33 +00:00
mdipierro 32650f0cbf R-2.14.2 2016-03-24 17:44:41 -05:00
mdipierro 8f8ef4cca5 fixed sessions for long keys 2016-03-24 16:58:04 -05:00
mdipierro c9e92fc686 restored pickles in sessions 2016-03-24 16:50:02 -05:00
mdipierro 0820926b50 more secure sessions in cookies using json 2016-03-24 16:46:51 -05:00
mdipierro 1856c9dc7a fixed changelog 2016-03-24 12:33:43 -05:00
mdipierro 973bb4a16f R-2.14.1 2016-03-24 12:20:58 -05:00
mdipierro 2fa54f069c Merge pull request #1246 from BuhtigithuB/improve-test-validators
Improve PEP8 just a bit + more coverage
2016-03-24 12:10:17 -05:00
mdipierro b11260d2e2 Merge pull request #1244 from mbelletti/fix/admin_enable_disable_apps
Fix missing w2p_ajax_disable_with_message #1234
2016-03-24 12:10:01 -05:00
mdipierro a6a9b004ea Merge pull request #1235 from kjkuan/avoid-query-per-row-for-virtual-fields
Avoid issuing a separate sql query for each row in sqlform grid search results, for virtual fields
2016-03-24 12:09:36 -05:00
mdipierro 6ed204301d ok dropped backward compatibility in redis_*.py 2016-03-24 12:08:43 -05:00
Richard Vézina 52392a10ae Improve PEP8 just a bit + more coverage 2016-03-24 10:44:37 -04:00
Jack Kuan 6d68a40ddf Fix to work in cases, in which the query for the grid is not a join. 2016-03-24 10:25:37 -04:00
Jack Kuan 51bf802978 When deriving the value for a virtual field in a sqlform grid,
avoid issuing a separate sql query for each row found in the search
results.
2016-03-24 10:25:37 -04:00
Massimiliano Belletti 34267b7673 Fix missing w2p_ajax_disable_with_message #1234 2016-03-24 11:17:59 +01:00
mdipierro 26eb5e6f38 Merge pull request #1242 from BuhtigithuB/fix-assert-raise-test-html
fix some assertRaises leave commented + more coverage
2016-03-24 03:03:24 -05:00
mdipierro 13964c2c9b Merge pull request #1241 from gi0baro/master
Updated pyDAL to 16.03
2016-03-24 03:03:03 -05:00
mdipierro 6a876fffc2 Merge pull request #1240 from BuhtigithuB/add-utils-test
test_utils inventory and new tests
2016-03-24 03:02:50 -05:00
mdipierro cd7850cc36 Merge pull request #1239 from BuhtigithuB/rework-fileutils-tests
rework test_fileutils.py mostly
2016-03-24 03:02:34 -05:00
Hardirc e4705dd48a fix some assertRaises leave commented + more coverage 2016-03-24 03:09:47 -04:00
Richard Vézina 2ef079289b test_utils inventory and new tests 2016-03-24 01:59:39 -04:00
mdipierro 76e95a9f1c edited CHANGELOG 2016-03-23 19:32:23 -05:00
mdipierro 4c5664f701 backward compatible syntax for redis 2016-03-23 19:14:37 -05:00
mdipierro cafba3fbe2 requires 2.14.1 2016-03-23 18:52:40 -05:00
Giovanni Barillari 8cb6678505 Updated pyDAL to 16.03 2016-03-24 00:38:48 +01:00
Richard Vézina 9fabfaac96 rework test_fileutils.py mostly 2016-03-23 14:32:14 -04:00
mdipierro 797ade202f Merge pull request #1238 from BuhtigithuB/add-more-test-over-html-py
BEAUTIFY, MENU, MARKMIN and ASSIGNJS basic tests and attempt ASSIGNJS…
2016-03-23 11:34:58 -05:00
Hardirc dda81d1b95 SELECT('Option 1', 'Option 2') what else... 2016-03-23 00:25:56 -04:00
Hardirc 13ed29ffc3 OPTGROUP with OPTION 2016-03-23 00:18:17 -04:00
Hardirc 785276d294 some more corner cases for SELECT 2016-03-23 00:16:27 -04:00
Hardirc 52f634cb58 some more corner cases for TEXTAREA and commented DIV SyntaxError 2016-03-22 23:50:23 -04:00
Hardirc f921b24a95 SELECT corner cases with option, multiple and selected 2016-03-22 23:25:37 -04:00
Hardirc 753c54dbfc HTML corner case for doctype=CustomDocType 2016-03-22 23:11:45 -04:00
Hardirc 89cf314358 XHTML corner tests for doctype=None/strict/transitional/frameset/xmlns 2016-03-22 22:47:59 -04:00
Hardirc 9299ecb64d HTML corner tests for doctype=stric/transitional/frameset/html5/None 2016-03-22 22:40:06 -04:00
Hardirc 94afe61477 BEAUTIFY, MENU, MARKMIN and ASSIGNJS basic tests and attempt ASSIGNJS documentation 2016-03-22 22:27:21 -04:00
mdipierro a22f56ca0c removed unwanted print statements 2016-03-22 17:55:29 -05:00
mdipierro 656c490a21 Merge pull request #1232 from BuhtigithuB/improve-grib-button-display
a space between grid button icon and text is much more beautiful
2016-03-22 17:15:25 -05:00
mdipierro 8859ef04d3 Merge pull request #1231 from leonelcamara/test_week3b
tests for basic Mail functionality
2016-03-22 17:15:06 -05:00
mdipierro 5cf835d856 fixed session_cookie_key leak 2016-03-21 17:25:16 -05:00
mdipierro 6a569bf56e restored some of the deleted files 2016-03-21 10:37:36 -05:00
mdipierro bd6115ad62 fixed Host header vulnerability #1196 2016-03-21 01:15:46 -05:00
Hardirc 3a265e3111 a space between grid button icon and text is much more beautiful 2016-03-20 14:05:40 -04:00
Leonel Câmara 78fc14df81 minor - pep8 and naming consistency 2016-03-20 12:46:26 +00:00
Leonel Câmara 4311820494 tests for basic Mail functionality 2016-03-20 12:33:38 +00:00
mdipierro d1094e7b0c 2.14.1.beta 2016-03-19 17:16:01 -05:00
mdipierro dbbbd44642 fixed space in bootstrap forms 2016-03-19 17:10:44 -05:00
mdipierro fa9d1ccb8b fixed color or calendar popup 2016-03-19 17:05:43 -05:00
mdipierro e6fad4f007 fixed default validators 2016-03-19 17:00:35 -05:00
mdipierro 23292754e3 fixed gae logging issue 2016-03-19 15:10:19 -05:00
mdipierro dcd7f8b46c no more copy of the logfile, thanks Luca 2016-03-19 14:29:01 -05:00
mdipierro 7fd67c4e2e fixed default validators ofr string and boolean 2016-03-19 14:15:08 -05:00
mdipierro ec3ae8211f possibly fixed #1162, validation of required notnull fields, hope this does not break something else 2016-03-19 13:39:27 -05:00
mdipierro e8c0e0df92 #1192 again, going it the way Anthony suggests 2016-03-19 13:24:06 -05:00
mdipierro 7f9262f8f8 partially addressed issue #1192, comments there 2016-03-19 13:10:23 -05:00
mdipierro 11da1ed19a fixed issue #1206, translate T.M 2016-03-19 13:02:45 -05:00
mdipierro 9735477c35 requires version 2016-03-19 12:05:19 -05:00
mdipierro f6f946f867 Merge pull request #1230 from leonelcamara/test_week3
test_IS_IMAGE and more
2016-03-19 11:29:58 -05:00
Leonel Câmara 8683b0680d add test for something that is NOT an image 2016-03-19 14:55:16 +00:00
Leonel Câmara fbb5776432 test_IS_IMAGE
Removed unused and undocumented IS_IN_SUBSET
Fixed _options IS_EMPTY_OR not passing arguments to the other options method
Some tests for formatters
2016-03-19 12:52:48 +00:00
mdipierro 92b4bc4f94 fixed #778 PRE(repr()) in admin, thanks Simone 2016-03-19 04:08:33 -05:00
mdipierro d886bf759e removed lots of junk from scripts folder 2016-03-19 04:02:22 -05:00
mdipierro d5d25e8110 jquery 1.12.2 2016-03-18 20:49:19 -05:00
mdipierro 86c70df1e7 updated style and spacing 2016-03-18 20:47:59 -05:00
mdipierro b6d923753a Merge pull request #1228 from cvaroqui/master
LDAP auth login method enhancements
2016-03-18 20:33:56 -05:00
mdipierro d4cad7634c Merge pull request #1227 from BuhtigithuB/update-feedparser-contrib
Update feedparser.py 5.1.3 -> 5.2.1
2016-03-18 20:33:24 -05:00
mdipierro 45c28b1d76 Merge pull request #1226 from BuhtigithuB/patch-1
Delete obsolete comment left behind
2016-03-18 20:32:39 -05:00
mdipierro b978bb90de Merge pull request #1225 from BuhtigithuB/pep8-test-web
Improve test_web.py PEP8
2016-03-18 20:32:16 -05:00
mdipierro e76ecec14f Merge pull request #1224 from BuhtigithuB/pep8-html-py
Improve PEP8
2016-03-18 20:31:15 -05:00
mdipierro 8ce528327c Merge pull request #1164 from BuhtigithuB/fix/flash-w2p-flash
flash -> w2p_flash
2016-03-18 20:26:33 -05:00
mdipierro fe0e23f4b9 Merge pull request #1223 from BuhtigithuB/add-missing-helpers-test
Add missing helpers test
2016-03-18 20:25:42 -05:00
mdipierro e05da97b1d Merge branch 'master' of github.com:web2py/web2py 2016-03-18 19:38:22 -05:00
mdipierro 5290308dea global options are back 2016-03-18 19:37:46 -05:00
Christophe Varoqui 5817a1893b LDAP auth login method enhancements
1/
Allow user to pass a group mapping in manage_groups=True mode.
A group mapping is a structure like:

{
  "ldap_grp1": ["web2py_grp1", "web2py_grp2"],
  "ldap_grp2": ["ldap_grp2", "web2py_grp3"],
}

After fetching the ldap user's ldap groups, the list entries are
translated, before janitoring the auth_group and auth_membership
tables.

2/
Allow to define a callback to execute if the auth_group or
auth_membership tables are changed.

Typical use-case are cache flushing and app-driven replication.
2016-03-18 21:24:08 +01:00
Richard Vézina aa2e302936 Update feedparser.py 5.1.3 -> 5.2.1 2016-03-18 12:04:27 -04:00
Hardirc d02755eac2 add basic tests for html.py 2016-03-17 11:57:08 -04:00
BuhtigithuB d037eaab44 Delete obsolete comment left behind
Comment at L111 :
         # COMMENTED BECAUSE FAILS BUT WHY?

Seems left behind and not useful anymore
2016-03-16 16:58:24 -04:00
Richard Vézina 9aa5995924 Improve test_web.py PEP8 2016-03-16 16:43:24 -04:00
Richard Vézina a981ca52e8 Improve PEP8 2016-03-16 09:38:26 -04:00
mdipierro 327c40cd17 Merge pull request #1218 from leonelcamara/test_week2
Tests for IS_IN_DB, IS_NOT_IN_DB, IS_URL (also tests IS_HTTP_URL IS_G…
2016-03-15 11:54:25 -05:00
mdipierro f9745e8a63 Merge pull request #1217 from niphlod/fix/redis_docstrings
fixes just docstrings about usage
2016-03-15 11:53:44 -05:00
mdipierro 51ce35c4e5 Merge pull request #1216 from BuhtigithuB/improve-rocket-test
create test_rocket.py with TODO inside about pathoc tests
2016-03-15 11:53:29 -05:00
mdipierro 0f7e4d774b Merge pull request #1215 from BuhtigithuB/improve-helper-test
Improve helper test
2016-03-15 11:53:11 -05:00
Richard Vézina 6e6612a57d add a todo about remain test() which may be deleted 2016-03-15 12:41:08 -04:00
Richard Vézina 83a3149849 Order tests in test_html.py todo for missing test + add many missing tests 2016-03-15 12:40:13 -04:00
Leonel Câmara 98d33bdded Tests for IS_IN_DB, IS_NOT_IN_DB, IS_URL (also tests IS_HTTP_URL IS_GENERIC_URL) 2016-03-14 23:59:08 +00:00
mdipierro f370187332 removed double json.loads(body), thanks Simone 2016-03-14 18:03:54 -05:00
niphlod 1b15b0c6dc fixes just docstrings about usage 2016-03-14 23:32:53 +01:00
mdipierro 507c3d6c33 fixed typo 2016-03-14 15:20:08 -05:00
mdipierro e62069c8b7 another error due to removing global settings from request 2016-03-14 15:13:10 -05:00
mdipierro d94ea6b295 simplified beautify example 2016-03-14 15:08:18 -05:00
mdipierro 9533978b37 fixed error in previous commit 2016-03-14 14:57:21 -05:00
mdipierro 9706d125b4 DO NOT LEAK GLOBALS SETTINGS INTO request object 2016-03-14 14:52:41 -05:00
Richard Vézina 01aa9de919 create test_rocket.py with TODO inside about pathoc tests 2016-03-14 15:28:50 -04:00
Richard Vézina 55994c489b Improve test naming convention test_ 2016-03-14 15:09:33 -04:00
Richard Vézina 983627daa4 Improve PEP8 test_html.py 2016-03-14 15:02:40 -04:00
mdipierro 7c299936e4 plugins link broken 2016-03-14 13:57:59 -05:00
mdipierro df55f52d8f fixed #1187 Field(.)Virtual typo, thanks RekGRpth 2016-03-14 12:45:01 -05:00
mdipierro c81f1fd6c8 reverting previous commit 2016-03-14 12:34:09 -05:00
mdipierro f15dd4b6e5 fixed #1204, updating session when add_membership 2016-03-14 12:32:34 -05:00
mdipierro e9e61cbca4 fixed #1213, custom password field name 2016-03-14 12:27:37 -05:00
mdipierro 700821e372 Merge pull request #1209 from zvolsky/transl_update_script
new script to update untranslated messages from other language file
2016-03-14 12:18:05 -05:00
mdipierro 1d04f8837e resorted tests as suggested by BuhtigithuB 2016-03-14 12:16:52 -05:00
mdipierro a375e047e9 Leonel new tests gluon/tests/test_validators.py 2016-03-14 12:04:47 -05:00
mdipierro fe6b222aaf Merge pull request #1212 from BuhtigithuB/improve-validators-test
Order tests to match validators.py and flag missing tests
2016-03-14 11:54:18 -05:00
mdipierro 0b5bb9b996 Merge pull request #1210 from zvolsky/nginx_uwsgi
nginx/ubuntu deploy script: added uWSGI Emperor configuration
2016-03-14 11:52:49 -05:00
mdipierro be4df0dee7 Merge pull request #1208 from zvolsky/czech_translation
updated czech translation (in admin and welcome)
2016-03-14 11:51:10 -05:00
mdipierro d877e8b6d0 Merge pull request #1207 from niphlod/fix/redis_scheduler
fixes the same bugs reported in #1191 for the "standard" scheduler
2016-03-14 11:50:49 -05:00
Richard Vézina 2531c2c640 Improve PEP8 2016-03-10 22:09:38 -05:00
Richard Vézina eca300af32 Order tests to match validators.py and flag missing tests 2016-03-10 21:56:46 -05:00
zvolsky 43c60df371 nginx/ubuntu deploy script: added uWSGI Emperor configuration 2016-03-10 10:20:36 +01:00
zvolsky cdac608efc new script to update untranslated messages from other language file 2016-03-10 09:48:04 +01:00
zvolsky 3311486b14 updated czech translation (in admin and welcome) 2016-03-10 09:35:18 +01:00
niphlod 038d0d17a4 fixes the same bugs reported in #1191 for the "standard" scheduler 2016-03-10 00:01:39 +01:00
mdipierro 3999fd80f8 removed unwanted icons 2016-03-09 12:41:12 -06:00
mdipierro 2db3975a32 social icons 2016-03-09 12:37:15 -06:00
mdipierro ebe3434a86 fixed commit error 2016-03-09 11:54:27 -06:00
mdipierro b487583f92 admin2 - experimental 2016-03-09 11:53:07 -06:00
mdipierro 702e7cbea2 added missing file 2016-03-09 11:50:48 -06:00
mdipierro 18a901cce4 fixed buttons in quick examples 2016-03-09 09:44:51 -06:00
mdipierro f23115cb9c better spacing and buttons examples 2016-03-09 09:35:01 -06:00
mdipierro ea5e86e11e powered by fixed 2016-03-09 09:25:52 -06:00
mdipierro db223dc70a examples in stupid.css 2016-03-09 09:24:47 -06:00
mdipierro bcc4ae2ec6 remporarily addressing issue #1203, thanks Simone 2016-03-08 17:22:02 -06:00
mdipierro 4b81f721ac Merge branch 'master' of github.com:web2py/web2py 2016-03-08 16:44:05 -06:00
mdipierro 66f231eb4b Merge pull request #1193 from CzechErface/master
Fixed a bug whereby calling 'delete_record()' on a row object caused …
2016-03-08 16:39:54 -06:00
mdipierro 5fc9517803 Merge pull request #1194 from chenl/master
invalid view for purely compiled app
2016-03-08 16:38:34 -06:00
mdipierro 98294e0c69 Merge pull request #1199 from boriscougar/master
Update imageutils.py
2016-03-08 16:37:20 -06:00
mdipierro 5e28112eda new welcome css 2016-03-02 08:47:58 -06:00
mdipierro dc5cac07e1 fixed bs3 form alignment 2016-03-01 23:39:08 -06:00
Boris Aramis Aguilar Rodríguez 8058dc2ce6 Update imageutils.py
Adding a padding statement into imageutils, the goal is to allow a padding transparent/white border on pictures when being resized in different aspect ratio.
2016-03-01 20:24:28 -06:00
mdipierro 3808b1f6ae optional parameters for setup-web2py-nginx-uwsgi-ubuntu.sh 2016-02-29 11:34:57 -06:00
mdipierro 10f2e4c3ad updates support.html, thanks Gael 2016-02-27 08:16:19 -06:00
mdipierro 3c6af5f920 updated support page 2016-02-27 00:12:01 -06:00
mdipierro a5269b1a1a Merge branch 'master' of github.com:web2py/web2py 2016-02-26 23:37:47 -06:00
mdipierro 9a079e092f fixed typo in auth 2016-02-26 14:24:21 -06:00
mdipierro 218817753a myconf.take, myconf.get 2016-02-26 14:20:18 -06:00
mdipierro ef9bf73973 example in rocket.py 2016-02-26 13:52:00 -06:00
mdipierro f92f21b060 Merge pull request #1195 from niphlod/fix/1191
fixes #1191
2016-02-25 13:59:14 -06:00
niphlod 642ec2b934 fixes #1191
fix is in lines 828-835 . needed to backport total_seconds for py2.6
(694-701).
everything else is just pep8.
2016-02-25 02:46:36 +01:00
Chen Levy d494ec9c88 fix invalid view for purly complied app
When packing a compiled app, and distributing it without the non-compiled view views, run_view_in searches only the old style compiled view file: e.g. views_default_index.html.pyc and not in views.default.index.html.pyc, resulting in "invalid view (default/index.html)" 404 Exception.
2016-02-22 14:13:02 +02:00
Jonathan Vasek c4d1f3f414 Fixed a bug whereby calling 'delete_record()' on a row object caused an AttributeError. 2016-02-21 00:19:49 -06:00
mdipierro 5a59149514 italian group 2016-02-12 16:03:16 -06:00
mdipierro 484f02cae1 fixed grid button alignment, thanks SanDiego 2016-02-10 16:04:27 -06:00
mdipierro d5db67d5ea Merge pull request #1182 from preactive/patch-1
Update ldap_auth.py
2016-02-07 00:14:31 -06:00
mdipierro 1480a10d6b Merge pull request #1176 from cccaballero/master
Added virtual field support to autocomplete widget
2016-02-07 00:14:22 -06:00
mdipierro 7259f273f3 fixed some body padding in welcome 2016-02-07 00:12:33 -06:00
mdipierro 8645365f58 fixed some button style issues in grid 2016-02-07 00:10:38 -06:00
mdipierro 106930ed73 fabfile now reads applications/app/hosts 2016-02-07 00:09:55 -06:00
preactive f79b38a335 Update ldap_auth.py
In regards to comments in:

https://github.com/BuhtigithuB/web2py/commit/0036d9c45bdf476d858fa00dd69e854c4623858b

And: 

https://github.com/web2py/web2py/issues/1178
2016-02-04 13:41:49 -08:00
mdipierro 35216db750 grid(Set(..)) as well as grid(Query(..)) 2016-02-03 22:11:38 -06:00
Carlos Cesar Caballero Díaz faa3d1d477 Added virtual field support to autocomplete widget
Added support to use Virtual Fields in autocomplete widget.

Gotchas:
- Using Virtual Fields is slower than normal fields.
- Virtual Fields must be declared with name and table_name attributes.
2016-01-30 10:20:37 -05:00
mdipierro 7aff79ca57 Merge pull request #1172 from dmatic/master
Fixed error with oneall login when login provider doesn't send user's full name
2016-01-29 22:20:51 -06:00
mdipierro 63bb4a7e8a Merge pull request #1171 from matclab/fix/1043
Fix #1170 by reverting fix to #1043
2016-01-29 22:20:26 -06:00
mdipierro 8fc322254e Merge pull request #1169 from BuhtigithuB/Improve/cache-redis-contrib
PEP8 enhancements, improve docstring and some vars rename
2016-01-29 22:18:28 -06:00
mdipierro b4c28516ae Merge pull request #1166 from Rimbo/overblown-sessions-cleanup
Make SessionSetDb.get() a generator.
2016-01-29 22:18:18 -06:00
mdipierro d233d3babb Merge pull request #1163 from rafaelol/fixes_mail_encoding_bug_for_non_ascii_text
Fix bug on Mail.send() when text or input are Unicode
2016-01-29 22:17:07 -06:00
mdipierro f18a1d0555 fabfile remove _update.zip 2016-01-29 22:14:38 -06:00
Dragan Matic 2cb55b52e9 fixed error with oneall login when login provider doesn't send full name 2016-01-17 22:45:38 +01:00
Mathieu Clabaut 4f361b5aad Fix #1170 by reverting fix to #1043
The problem is only a styling issue and not a HTML one.

It was solved  in 864dbe73f2 by adding a `readonly ` class to labels which allow for properly vertical align with CSS.

This reverts commit 353db90a64 which add
specific HTML for some filed types and which breaks display of comments.
2016-01-16 10:02:12 +01:00
Dragan Matic db122e7709 Merge pull request #2 from web2py/master
update from original
2016-01-15 17:05:39 +01:00
Richard Vézina 005e565a11 PEP8 enhancements, improve docstring and some vars rename 2016-01-14 12:28:05 -05:00
Jimmy Rimmer 1656c6cdeb Make the same change for SessionSetDb that's in SessionSetFiles: Make get() a generator, so that memory doesn't get blown out if there are 18 million sessions there to clean up. 2016-01-11 14:09:25 -08:00
mdipierro b7a0f2043c fixed fabfile error 2016-01-09 22:20:42 -06:00
mdipierro 05df3b3029 smarter request.restful checks content-type for json body 2016-01-08 21:29:04 -06:00
Richard Vézina dc1c85928d flash -> w2p_flash 2016-01-08 13:13:13 -05:00
rafaelol ba2cb811be Changes encoding of text and subject on Mail.send()
On the previous commit we changed text and subject from unicode
to str. After a better solution from @cassiobotaro, we're using
unicode again, selecting the encoding as the one passed via encoding
parameter.
2016-01-07 14:59:58 -02:00
rafaelol 6a7c0525f5 Fix bug on Mail.send() when text or input are Unicode
On PR #964 @matclab forced the encoding of both subject and
text variables to unicode.

After merging it, matclab realized that when we send Unicode
text to the method it raises an exception and asked if he should
change the commit. Unfortunately this thing was kept untouched.

This problem exists because we previously encode the unicode variables
to utf-8 (for instance here https://github.com/web2py/web2py/blob/master/gluon/tools.py#L478-L481) and then force again to unicode. This piece of code shows what happens:

```
>>> a = u'áéí'
>>> a
u'\xe1\xe9\xed'
>>> b = a.encode('utf-8')
>>> b
'\xc3\xa1\xc3\xa9\xc3\xad'
>>> unicode(a)
u'\xe1\xe9\xed'
>>> unicode(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
```

If we force to str, just like @matclab suggested, we solve this issue.
2016-01-07 11:47:29 -02:00
mdipierro 5132616c6c check_all 2016-01-05 07:29:53 -06:00
mdipierro e528c10c21 Merge pull request #1160 from sceeter89/portalocker-issues
Changed order of imports
2016-01-04 09:44:20 -06:00
mdipierro 41fd02fa2c Merge pull request #1158 from niphlod/enhancement/redis_toolset
new redis toolset to use with web2py
2016-01-04 09:43:58 -06:00
mdipierro 26dab37d9f Merge pull request #1157 from niphlod/fix/scheduler_stopped
fix for STOPPED tasks via stop_task()
2016-01-04 09:43:13 -06:00
mdipierro cc40018e87 Merge pull request #1155 from niphlod/tests/recfile
tests for modules NEED to be in a separate unittest
2016-01-04 09:42:23 -06:00
mdipierro b6db314612 Merge pull request #1154 from niphlod/fix/1044
fixes #1044
2016-01-04 09:41:58 -06:00
mdipierro 3498666115 Merge pull request #1153 from niphlod/fix/1100
fixes #1100
2016-01-04 09:41:43 -06:00
mdipierro 562a559169 Merge pull request #1152 from niphlod/fix/1143
fixes #1143
2016-01-04 09:40:35 -06:00
mdipierro 47cec80939 Merge pull request #1151 from niphlod/tests/reorg
better test_utils and reorg to compileapp
2016-01-04 09:39:59 -06:00
mdipierro eceb579cdd Merge pull request #1149 from niphlod/enhancement/constant_time_compare
bultin constant time checking
2016-01-04 09:39:38 -06:00
Adam Marszałek bd19986380 Changed order of imports 2016-01-04 13:23:22 +01:00
niphlod 12acdb51d7 new redis toolset to use with web2py
This is a refactor of everything web2py uses with redis.
Specifically:
- a refactored redis_cache.py that fixes #958, allowing a "fail
gracefully" behaviour in case redis is not available
- a refactored redis_session.py that blocks less with with_lock=True
(although still not optimal)
- a new (and NEEDED) redis_utils.py that serves as the base for
everything else, allowing an RConn object that you can freely use as a
redis.StrictRedis connection, and that you can override in case you're
using a different library (or that web2py will use in case redis-py
won't be the de-facto standard around)
- a newly - and much anticipated - redis_scheduler.py. It's a slip-in
replacement for the standard scheduler that uses redis for workers
coordination. Feel free to dig in the code and improve it.

For redis_cache and redis_session changes are BREAKING. It means that
users will need to change the import locations and tune a bit the code.
Now every module depends on an gluon.contrib.redis_utils.RConn object
(or similar) that in turns is very similar to a redis.StrictRedis one.
The redis instance is EXTERNAL to the modules themselves (no more "host,
port, db, password" parameters in RedisCache or RedisSession)
See the relevant docstrings for usage examples
2016-01-02 23:15:00 +01:00
niphlod 918590d1f3 fix for STOPPED tasks via stop_task()
they previously killed the main process
2016-01-02 22:50:27 +01:00
niphlod d57428e8f0 fixes #1156 and other few issues 2016-01-01 20:48:55 +01:00
niphlod 13e3adf22d tests for modules NEED to be in a separate unittest 2015-12-30 21:20:24 +01:00
niphlod d4ffcaf1b1 fixes #1044
This shouldn't have took that much to solve. Really sorry for the delay
2015-12-30 16:38:43 +01:00
niphlod 17f1a51133 fixes #1100
now elements with data-w2p_disable are not disabled.
in addition, for non-ajax forms, disabled submit buttons are reenabled
after 5 seconds

plus reindented, refactored AND jslinted
2015-12-30 16:19:39 +01:00
niphlod d4bca008a8 better docstrings 2015-12-30 14:55:37 +01:00
niphlod 90c33911ab fixes #1143
don't even get me started about the current messy status of admin
2015-12-30 12:07:18 +01:00
niphlod 0a263ffc8d better test_utils and reorg to compileapp 2015-12-30 11:42:49 +01:00
niphlod e94946d3d5 bultin constant time checking
- if hmac.compare_digest is there, we should use it instead of our own
fallback.
- jwt handler has been updated to use utils.compare (reported in
#web2py-users)
- includes the same mods as https://github.com/web2py/web2py/pull/1146
2015-12-30 10:37:14 +01:00
mdipierro 1ca0c9b0c0 Merge pull request #1145 from niphlod/fix/1142
fixes #1142
2015-12-29 12:07:40 -06:00
mdipierro cee0f91b36 Merge pull request #1144 from niphlod/fix/external_folder
better support for -f
2015-12-29 12:06:54 -06:00
niphlod eb49831726 fixes #1142 2015-12-28 15:05:51 +01:00
niphlod b517c898b8 better support for -f
as exposed in
https://groups.google.com/d/msg/web2py-developers/uJoQeUlCABw/-MCXocwGAgAJ
2015-12-28 14:48:29 +01:00
113 changed files with 7787 additions and 8597 deletions
+50 -2
View File
@@ -1,9 +1,57 @@
## 2.13.1-2
## 2.14.1
- fixed two major security issues that caused the examples app to leak information
- new Auth(…,host_names=[…]) to prevent host header injection
- improved scheduler
- pep8 enhancements
- many bug fixes
- restored GAE support that was broken in 2.13.*
- improved fabfile for deployment
- refactored examples with stupid.css
- new JWT implementation (experimental)
- new gluon.contrib.redis_scheduler
- myconf.get
- LDAP groups (experimental)
- .flash -> .w2p_flash
- Updated feedparser.py 5.2.1
- Updated jQuery 1.12.2
- welcome app now checks for version number
- Redis improvements. New syntax:
BEFORE:
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_cache import RedisCache
rconn = RConn()
# or RConn(host='localhost', port=6379,
# db=0, password=None, socket_timeout=None,
# socket_connect_timeout=None, .....)
# exactly as a redis.StrictRedis instance
cache.redis = RedisCache(redis_conn=rconn, debug=True)
BEFORE:
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
session.connect(request, response, db = sessiondb)
NOW:
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_session import RedisSession
rconn = RConn()
sessiondb = RedisSession(redis_conn=rconn, session_expiry=False)
session.connect(request, response, db = sessiondb)
## 2.13.*
- fixed a security issue in request_reset_password
- added fabfile.py
- fixed oauth2 renew token, thanks dokime7
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
- fixed add_membership, del_membership, add_membership IntegrityError (when auth.enable_record_versioning)
- allow passing unicode to template render
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
- allow HttpOnly cookies
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.14.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.13.3-stable+timestamp.2015.12.24.08.08.22
Version 2.14.3-stable+timestamp.2016.03.26.17.54.43
+3 -3
View File
@@ -268,7 +268,7 @@ def site():
raise Exception("404 file not found")
except Exception, e:
session.flash = \
DIV(T('Unable to download app because:'), PRE(str(e)))
DIV(T('Unable to download app because:'), PRE(repr(e)))
redirect(URL(r=request))
fname = form_update.vars.url
@@ -740,7 +740,7 @@ def edit():
B(ex_name), ' ' + T('at line %s', e.lineno),
offset and ' ' +
T('at char %s', offset) or '',
PRE(str(e)))
PRE(repr(e)))
if data_or_revert and request.args[1] == 'modules':
# Lets try to reload the modules
try:
@@ -751,7 +751,7 @@ def edit():
% (request.args[0], mopath)])
except Exception, e:
response.flash = DIV(
T('failed to reload module because:'), PRE(str(e)))
T('failed to reload module because:'), PRE(repr(e)))
edit_controller = None
editviewlinks = None
+340 -104
View File
@@ -2,89 +2,131 @@
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". (Avšak výsledky databázového JOINu nelze mazat ani upravovat.)',
'"User Exception" debug mode. ': '"Uživatelská výjimka", Debug mód.',
'"User Exception" debug mode. An error ticket could be issued!': '"Uživatelská výjimka", Debug mód. Může být vystaven chybový tiket.',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s': '%s',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%s students registered': '%s studentů registrováno',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(requires internet access, experimental)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)',
'(something like "it-it")': '(například "cs-cs")',
'(version %s)': '(verze %s)',
'?': '?',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'Abort': 'Ukončit',
'About': 'O programu',
'About application': 'O aplikaci',
'Accept Terms': 'Souhlasit s podmínkami',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Admin design page': 'Admin design page',
'Additional code for your application': 'Další kód pro Vaši aplikaci (pro příkaz import). Neběží ve specifickém režimu ani ve vláknech jako model/kontrolér/šablona, ale jako standardní python moduly. Ty tedy můžete umístit sem (pouze pro tuto aplikaci) nebo používat systémově dostupné.',
'Admin design page': 'Admin design stránka',
'admin disabled because no admin password': 'admin je zakázán, protože chybí heslo administrátora',
'admin disabled because not supported on google app engine': 'admin je zakázán kvůli chybějící podpoře na Google App Engine',
'admin disabled because too many invalid login attempts': 'Admin je zakázán po příliš mnoha nesprávných pokusech o přihlášení',
'admin disabled because unable to access password file': 'Admin je zakázán, protože nelze číst soubor s heslem',
'Admin is disabled because insecure channel': 'Admin je zakázán na nezabezpečeném připojení',
'Admin language': 'jazyk rozhraní',
'Admin versioning page': 'Admin verzovací stránka',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'An error occured, please %s the page',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku',
'and rename it:': 'a přejmenovat na:',
'App does not exist or you are not authorized': 'Aplikace neexistuje nebo vám chybí oprávnění',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Application',
'Application': 'Aplikace',
'application "%s" uninstalled': 'application "%s" odinstalována',
'Application cannot be generated in demo mode': 'Aplikace nemůže být vytvořena v demo módu',
'application compiled': 'aplikace zkompilována',
'Application exists already': 'Aplikace již existuje',
'application is compiled and cannot be designed': 'aplikace je přeložena a nelze ji editovat',
'Application name:': 'Název aplikace:',
'Application updated via git pull': 'Aplikace byla aktualizována pomocí git pull',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete file "%s"?': 'Skutečně chcete smazat soubor "%s"?',
'Are you sure you want to delete plugin "%s"?': 'Skutečně chcete smazat plugin "%s"?',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
'ATTENTION:': 'ATTENTION:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Are you sure?': 'Jste si jist(a)?',
'arguments': 'argumenty',
'at char %s': 'na pozici znaku %s',
'at line %s': 'na řádku %s',
'ATTENTION:': 'POZOR:',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'POZOR: Přihlášení vyžaduje zabezpečené (HTTPS) připojení nebo spouštění na localhost.',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'POZOR: TESTOVÁNÍ NENÍ BEZPEČNÉ PŘI SOUBĚŽNÝCH VLÁKNECH. NESPOUŠTĚJ VÍCE TESTŮ SOUBĚŽNĚ.',
'ATTENTION: you cannot edit the running application!': 'POZOR: Nelze editovat spuštěnou aplikaci.',
'Autocomplete Python Code': 'Autocomplete Python kód',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Back to wizard',
'Basics': 'Basics',
'Back to the plugins list': 'Zpět do seznamu pluginů',
'Back to wizard': 'Zpátky do průvodce',
'Basics': 'Základy',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Bulk Register': 'Hromadná registrace',
'Bulk Student Registration': 'Hromadná registrace studentů',
'Buy this book': 'Koupit web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Cleared': 'Cache byla vymazána',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Cannot compile: there are errors in your app:': 'Nelze zkompilovat: ve vaší aplikaci jsou chyby:',
'cannot create file': 'nelze vytvořit soubor',
'cannot upload file "%(filename)s"': 'nelze nahrát soubor "%(filename)s"',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'change editor settings': 'změnit nastavení editoru',
'Change password': 'Změna hesla',
'Changelog': 'Žurnál změn',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear': 'Inicializovat',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'code',
'Code listing': 'Code listing',
'code': 'kód',
'Code listing': 'Výpis kódu',
'collapse/expand all': 'vše sbalit/rozbalit',
'Command': 'Příkaz',
'Comment:': 'Komentář:',
'Commit': 'Potvrdit',
'Commit form': 'Potvrdit formulář',
'Committed files': 'Potvrzené soubory',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'Compile (all or nothing)': 'Přeložit (vše nebo nic)',
'Compile (skip failed views)': 'Přeložit (přeskočit chybné šablony)',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'continue': 'continue',
'continue': 'pokračovat',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
@@ -92,9 +134,12 @@
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'Create/Upload': 'Vytvořit/Nahrát',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created by:': 'Vytvořil:',
'Created On': 'Vytvořeno - kdy',
'Created on:': 'Vytvořeno:',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
@@ -105,18 +150,19 @@
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Database administration',
'Database administration': 'Administrace databáze',
'database administration': 'správa databáze',
'Database Administration (appadmin)': 'Administrace databáze (appadmin)',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'defines tables',
'defines tables': 'definuje tabulky',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'delete plugin',
'delete plugin': 'zrušit plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
@@ -124,166 +170,265 @@
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deploy to pythonanywhere': 'Nahrát na PythonAnywhere',
'Deploy to PythonAnywhere': 'Nahrát na PythonAnywhere',
'Deployment form': 'Forumlář pro deployment (nasazení)',
'Deployment Interface': 'Rozhraní pro deployment (nasazení)',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'Description:': 'Popis:',
'design': 'návrh',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'directory not found': 'adresář nebyl nalezen',
'Disable': 'Zablokovat',
'Disabled': 'Blokováno',
'disabled in demo mode': 'zakázáno v demo módu',
'disabled in GAE mode': 'zakázáno v GAE módu',
'disabled in multi user mode': 'zakázáno ve víceuživatelském módu',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'Display line numbers': 'Zobrazit čísla řádků',
'DO NOT use the "Pack compiled" feature.': 'NEPOUŽÍVEJ vlastnost "Zabalit zkompilované".',
'docs': 'dokumentace',
'Docs': 'Dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Nevíte kudy kam?',
'done!': 'hotovo!',
'Downgrade': 'Downgrade (vrácení verze)',
'Download': 'Stáhnout',
'Download .w2p': 'Stažení .w2p',
'Download as .exe': 'Stáhnout jako .exe',
'download layouts': 'stáhnout moduly rozvržení stránky',
'Download layouts from repository': 'Stáhnout moduly rozvržení z repozitáře',
'download plugins': 'stáhnout zásuvné moduly',
'Download plugins from repository': 'Stáhnout pluginy z repozitáře',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'edit all',
'edit all': 'editovat vše',
'Edit application': 'Správa aplikace',
'edit controller': 'edit controller',
'edit controller': 'editovat controller',
'edit controller:': 'editovat kontrolér:',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'edit views:': 'upravit šablonu (view):',
'Editing %s': 'Editace %s',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Editing Plural Forms File': 'Editování souboru množných čísel',
'Editor': 'Editor',
'Email Address': 'Emailová adresa',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'Enable Close-Tag': 'Povolit Close-Tag',
'Enable Code Folding': 'Povolit sdružování kódu',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Error ticket': 'Tiket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s',
'Exception %s': 'Výjimka %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expand Abbreviation',
'Exit Fullscreen': 'Ukončit režim celé obrazovky',
'Expand Abbreviation': 'Rozvinout zkratku',
'Expand Abbreviation (html files only)': 'Rozvinout zkratku (pouze html soubory)',
'export as csv file': 'exportovat do .csv souboru',
'Exports:': 'Exporty:',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'failed to reload module because:': 'nepodařilo se restartovat modul, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'file "%(filename)s" created',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen',
'file "%(filename)s" deleted': 'soubor "%(filename)s" byl zrušen',
'file "%(filename)s" uploaded': 'soubor "%(filename)s" byl nahrán',
'file "%s" of %s restored': 'soubor "%s" z %s byl obnoven',
'file changed on disk': 'soubor se na disku změnil',
'file does not exist': 'soubor neexistuje',
'file not found': 'soubor nebyl nalezen',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'filename': 'jméno souboru',
'Filename': 'Název souboru',
'Files added': 'Soubory byly přidány',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Form has errors': 'Ve formuláři jsou chyby',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Frames',
'Frames': 'Framy',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Functions with no doctests will result in [passed] tests.': 'Funkce bez doctestů se projeví jako [úspěšný] test.',
'GAE Email': 'GAE e-mail',
'GAE Output': 'GAE výstup',
'GAE Password': 'GAE heslo',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Goto',
'graph model': 'graph model',
'Google App Engine Deployment Interface': 'Google App Engine - rozhraní pro nasazení',
'Google Application Id': 'ID Google Aplikace',
'Goto': 'Přejít na',
'graph model': 'grafický model',
'Graph Model': 'Grafický model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj světe',
'Help': 'Nápověda',
'here': 'zde',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Highlight current line': 'Zvýraznit aktuální řádek',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak jste se sem vlastně dostal?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'If start the downgrade, be patient, it may take a while to rollback': 'Spustíte-li downgrade verze, vyčkejte, protože vrácení změn může trvat dlouho',
'If start the upgrade, be patient, it may take a while to download': 'Spustíte-li upgrade, vyčkejte, protože stahování může trvat dlouho',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\n\t\tA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'Jestliže přehled výše obsahuje číslo chybového tiketu, znamená to chybu v kontroléru, ještě před pokusem vykonat doctesty. (Často je to způsobeno chybou odsazení nebo chybou mimo kód funkce.) Zelený nadpis označuje, že žádný test nehavaroval. (V tom případě dílčí výsledky nejsou uvedeny.)',
'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'Jestliže vaše aplikace používá jinou databázi než SQLite, budete muset na PythonAnywhere konfigurovat její DAL() připojení.',
'import': 'import',
'Import/Export': 'Import/Export',
'In development, use the default Rocket webserver that is currently supported by this debugger.': 'Při vývoji je doporučeno použít předvolený webserver Rocket, se kterým tento debugger spolupracuje.',
'includes': 'zahrnuje',
'Indent with tabs': 'Odsazení tabelátory',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'inspect attributes',
'inspect attributes': 'prohlédnout atributy',
'Install': 'Instalovat',
'Installation of %(plugin)s for %(app)s': 'Instalace %(plugin)s pro %(app)s',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'internal error': 'vnitřní chyba',
'internal error: %s': 'vnitřní chyba: %s',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid action': 'Chybná akce',
'Invalid application name': 'Nesprávné jméno aplikace',
'invalid circular reference': 'nepovolený kruhový odkaz',
'Invalid email': 'Neplatný email',
'Invalid git repository specified.': 'Byl zadán nesprávný git repozitář.',
'Invalid password': 'Nesprávné heslo',
'invalid password': 'nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Invalid request': 'Nesprávný požadavek (request)',
'invalid table names (auth_* tables already defined)': 'chybná jména tabulek (auth_* tabulky už byly definovány)',
'invalid ticket': 'chybný tiket',
'Is Active': 'Je aktivní',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'Key bindings': 'Vazby kláves',
'Key bindings for ZenCoding Plugin': 'Vazby kláves pro ZenCoding Plugin',
'Keyboard shortcuts': 'Klávesové zkratky',
'kill process': 'likvidovat proces',
'language file "%(filename)s" created/updated': 'jazykový soubor "%(filename)s" byl vytvořen/aktualizován',
'Language files (static strings) updated': 'Jazykové soubory (statické řetězce) byly aktualizovány',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjmení',
'Last Revision': 'Minulá verze',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'License:': 'Licence:',
'Line Nr': 'Č.řádku',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online pokec',
'lists by exception': 'výpis podle výjimky',
'lists by ticket': 'výpis podle tiketu',
'Live Chat': 'Online chat',
'Loading...': 'Nahrávám...',
'loading...': 'nahrávám...',
'locals': 'locals',
'Local Apps': 'Lokální aplikace',
'locals': 'lokální proměnné',
'Locals##debug': 'Lokální proměnné',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login': 'Přihlásit se',
'Login successful': 'Přihlášení bylo úspěšné',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'Login/Register': 'Přihlásit se / Registrovat',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'lost password': 'ztracené heslo',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Manage',
'Manage Cache': 'Manage Cache',
'Main Menu': 'Hlavní nabídka',
'Manage': 'Spravovat',
'Manage %(action)s': 'Spravovat %(action)s',
'Manage Access Control': 'Spravovat řízení přístupu',
'Manage Admin Users/Students': 'Spravovat Admin uživatele / Studenty',
'Manage Cache': 'Spravovat cache',
'Manage Students': 'Spravovat studenty',
'Memberships': 'Členství ve skupinách',
'Menu Model': 'Model rozbalovací nabídky',
'merge': 'sloučit',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'Multi User Mode': 'Víceuživatelský mód',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'new application "%s" imported': 'nová aplikace "%s" byla importována',
'New Application Wizard': 'Nový průvodce aplikací',
'New application wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'new plugin installed': 'nový plugin byl instalován',
'New plugin installed: %s': 'Nový plugin byl instalován: %s',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit primitivní aplikaci',
'next': 'next',
'New simple application': 'Vytvořit novou aplikaci',
'next': 'další',
'next %s rows': 'dalších %s řádků',
'next 100 rows': 'dalších 100 řádků',
'NO': 'NE',
'no changes': 'beze změn',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'no match': 'nenalezena shoda',
'no package selected': 'nebyla vybrána žádná package',
'no permission to uninstall "%s"': 'chybí oprávnění odinstalovat "%s"',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Node:': 'Uzel (node):',
'Not Authorized': 'Chybí autorizace',
'Not supported': 'Není podporováno',
'Note: If you receive an error with github status code of 128, ensure the system and account you are deploying from has a cooresponding ssh key configured in the openshift account.': 'Poznámka: Dostanete-li chybu s github status code = 128, ujistěte se, že systém a účet z něhož provádíte nasazení má odpovídající ssh klíč, konfigurovaný v OpenShift účtu.',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
"On production, you'll have to configure your webserver to use one process and multiple threads to use this debugger.": 'Pro použití tohoto debuggeru na produkci je potřeba konfigurovat webserver, aby používal jeden proces a více vláken.',
'online designer': 'online návrhář',
'Online examples': 'Příklady online',
'Open new app in new window': 'Open new app in new window',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně',
'OpenShift Deployment Interface': 'OpenShift rozhraní pro nasazení aplikace',
'OpenShift Output': 'OpenShift výstup',
'or alternatively': 'nebo případně',
'Or Get from URL:': 'Nebo získat z URL adresy:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
@@ -293,30 +438,53 @@
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack plugin',
'password': 'heslo',
'Pack custom': 'Zabalit volitelně (custom)',
'pack plugin': 'zabalit plugin',
'Password': 'Heslo',
'password': 'heslo',
'password changed': 'heslo bylo změněno',
"Password fields don't match": 'Hesla se neshodují',
'Peeking at file': 'Peeking at file',
'Past revisions': 'Minulá verze',
'Path to appcfg.py': 'Cesta ke appcfg.py',
'Path to local openshift repo root.': 'Cesta ke kořenu (rootu) lokálního OpenShift repozitáře.',
'Peeking at file': 'Sledování souboru',
'Permission': 'Oprávnění',
'Permissions': 'Oprávnění',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" in application',
'Please wait, giving pythonanywhere a moment...': 'Prosím, čekejte na dokončení činnosti PythonAnywhere...',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" byl odstraněn',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci',
'plugin not specified': 'plugin nebyl určen',
'Plugin page': 'Stránka pluginů',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Plural Form #%s',
'Plural Form #%s': 'Množné číslo #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Poháněno',
'Powered by': 'používá technologii',
'Preface': 'Předmluva',
'Preferences saved correctly': 'Nastavení byla úspěšně uložena',
'Preferences saved on session only': 'Nastavení byla uložena pouze pro toto sezení',
'previous %s rows': 'předchozích %s řádků',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Pull': 'Pull',
'Pull failed, certain files could not be checked out. Check logs for details.': 'Pull selhal, některé soubory nelze zkopírovat. Pro podrobnosti zkontrolujte logy.',
'Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.': 'Pull nelze provést, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'Push': 'Push',
'Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.': 'Push selhal, protože máte nesloučené soubory. Vyřešte tyto konflikty a pak akci opakujte.',
'pygraphviz library not found': 'pygraphviz knihovna nebyla nalezena',
'Python': 'Python',
'PythonAnywhere Apps': 'PythonAnywhere aplikace',
'PythonAnywhere Password': 'PythonAnywhere heslo',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Rapid Search': 'Rychlé hledání',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
@@ -335,114 +503,167 @@
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Repository (%s)': 'Repozitář (%s)',
'request': 'požadavek (request)',
'requires distutils, but not installed': 'vyžaduje distutils, jenže ty nejsou instalovány',
'requires python-git, but not installed': 'vyžaduje python-git, který ale není nainstalován',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'Resolve Conflict file': 'Vyřešit konflikty',
'response': 'odpověď (response)',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'Revert': 'Vrátit se k původnímu',
'revert': 'vrátit se k původnímu',
'reverted to revision %s': 'vráceno k verzi %s',
'Revision %s': 'Verze %s',
'Revision:': 'Verze:',
'Role': 'Role',
'Roles': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
'Run tests': 'Spustit testy',
'Run tests in this file': 'Spustit testy v souboru',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Save file:',
'Save file:': 'Uložit soubor:',
'Save file: %s': 'Uložit soubor: %s',
'Save model as...': 'Uložit model jako...',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Screenshot %s': 'Screenshot %s',
'Search': 'Hledání',
'Select Files to Package': 'Vybrat soubory pro package',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'session expired',
'session': 'session (sezení)',
'session expired': 'vypršela session',
'Session saved correctly': 'Session byla úspěšně uložena',
'Session saved on session only': 'Session byla uložena jen pro toto sezení',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Singular Form': 'Singular Form',
'Showing %s to %s of %s %s found': 'Zobrazuji %s%s z %s %s nalezených',
'Singular Form': 'Jednotné číslo',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'skip to generate',
'skip to generate': 'přeskočit pro vytvoření',
'some files could not be removed': 'některé soubory nelze odstranit',
'Something went wrong please wait a few minutes before retrying': 'Něco se nepodařilo. Vyčkejte několik minut a pak zkuste znova',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'source : db': 'zdroj : db',
'source : filesystem': 'zdroj : souborový systém',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Static',
'Static': 'Statické soubory',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Step',
'step': 'step',
'stop': 'stop',
'Step': 'Krok',
'step': 'krok',
'stop': 'zastavit',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'switch to : db': 'přepnout na : db',
'switch to : filesystem': 'přepnout na : souborový systém',
'Tab width (# characters)': 'Šířka tabelátoru (# znaků)',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Testing application',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'Testing application': 'Zkušební aplikace',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je například "db.tabulka1.pole1==\'hodnota\'". Dotaz se dvěma tabulkami "db.tabulka1.pole1==db.tabulka2.pole2" vytvoří SQL JOIN.',
'The app exists, was created by wizard, continue to overwrite!': 'Aplikace existuje, byla vytvořena průvodcem. Pokračováním ji přepíšete !',
'The app exists, was NOT created by wizard, continue to overwrite!': 'Aplikace existuje, a NEBYLA vytvořena průvodcem. Pokračováním ji přepíšete !',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The data representation, define database tables and sets': 'Modely se vykonají při každém přístupu. Zde se obvykle definuje především reprezentace dat: Připojení k databázi a struktura tabulek databáze',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je dictionary (slovník), který se zobrazil pomocí šablony (view) %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: šablony (neboli pohledy, templaty, view). Mixuje Html, Python kód a Python data.',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'There are no controllers',
'There are no modules': 'There are no modules',
'Theme': 'Téma',
'There are no controllers': 'Nejsou vytvořeny žádné controllery',
'There are no models': 'Není vytvořen žádný model',
'There are no modules': 'Nejsou přidány žádné moduly',
'There are no plugins': 'Žádné moduly nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'There are no static files',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'There are no views',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'There are no static files': 'Nejsou přidány žádné statické soubory',
'There are no translators': 'Není vytvořen žádný překlad',
'There are no translators, only default language is supported': 'Není vytvořen žádný překlad, je podporován jen defaultní jazyk',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou přístupné jen běžící aplikaci. Nejsou dostupné uživatelům, ani se nekopírují do případného vývojového repozitáře. Hesla a citlivá nastavení nedávejte nikam jinam.',
'These files are served without processing, your images go here': 'Tyto soubory jsou stahovány přímo, bez jakékoli přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'This is the %(filename)s template',
"This debugger may not work properly if you don't have a threaded webserver or you're using multiple daemon processes.": 'Tento debugger nebude pracovat správně, jestliže váš webový server nepracuje pomocí vláken nebo když používáte více procesů démonů.',
'This is a copy of the scaffolding application': 'Toto je kopie vzorové aplikace.',
'This is an experimental feature and it needs more testing. If you decide to downgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Návrat verze jen na vlastní riziko.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'Toto je experimentální vlastnost, která vyžaduje další testování. Upgrade verze jen na vlastní riziko.',
'This is the %(filename)s template': 'Toto je šablona %(filename)s',
"This page can commit your changes to an openshift app repo and push them to your cloud instance. This assumes that you've already created the application instance using the web2py skeleton and have that repo somewhere on a filesystem that this web2py instance can access. This functionality requires GitPython installed and on the python path of the runtime that web2py is operating in.": 'Tato stránka umožňuje potvrdit vaše změny do OpenShift aplikačního repozitáře a odeslat je do vaší cloud instance. Předpokladem je, že jste už vytvořili aplikační instanci pomocí Web2py předlohy a že tento repozitář máte někde na disku tak, aby k němu Web2py mělo přístup. Tato funkcionalita vyžaduje, aby byl instalován GitPython a aby mohl být nalezen pomocí cesty, se kterou Web2py pracuje.',
'This page can upload your application to the Google App Engine computing cloud. Mind that you must first create indexes locally and this is done by installing the Google appserver and running the app locally with it once, or there will be errors when selecting records. Attention: deployment may take long time, depending on the network speed. Attention: it will overwrite your app.yaml. DO NOT SUBMIT TWICE.': 'Tato stránka umožňuje zkopírovat vaši aplikaci do Google App Engine cloudu. Pamatujte, že nejprve je třeba vytvořit indexy lokálně, čehož dosáhnete instalací Google appserver a jedním lokálním spuštěním aplikace. V opačném případě bude docházet k chybám vyhledávání. Pozor: v závislosti na rychlosti sítě může nasazení trvat dlouhou dobu. Pozor: bude přepsán váš soubor app.yaml. BĚHEM SPUŠTĚNÍ NESPOUŠTĚJTE PODRUHÉ.',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'This will pull changes from the remote repo for application "%s"?': 'Toto stáhne (pull) změny ze vzdáleného repozitáře pro aplikaci "%s"?',
'This will push changes to the remote repo for application "%s".': 'Toto nahraje (push) změny do vzdáleného repozitáře pro aplikaci "%s".',
'Ticket': 'Tiket',
'Ticket ID': 'ID tiketu',
'Ticket Missing': 'Chybový tiket chybí',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete skupinu souborů nebo adresář(e) plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle comment': 'Přepnout komentář',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Traceback',
'Traceback': 'Hierarchie volání',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'try something like',
'try something like': 'zkuste něco jako',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'try view',
'try view': 'vyzkoušet šablonu (view)',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Unable to check for upgrades',
'Type PDB debugger command in here and hit Return (Enter) to execute it.': 'Zapište příkaz PDB debuggeru a stiskněte Return (Enter) pro jeho provedení.',
'Type python statement in here and hit Return (Enter) to execute it.': 'Zapište příkaz pythonu a stiskněte Return (Enter) pro jeho provedení.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Zapište kód v jazyce python a stiskněte Return (Enter) pro jeho provedení.',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích',
'unable to create application "%s"': 'nelze vytvořit aplikaci "%s"',
'unable to delete file "%(filename)s"': 'nelze zrušit soubor "%(filename)s"',
'unable to delete file plugin "%(plugin)s"': 'nelze zrušit plugin "%(plugin)s"',
'Unable to determine the line number!': 'Nelze určit číslo řádky!',
'Unable to download app because:': 'Nelze stáhnout aplikaci, protože:',
'unable to download layout': 'nelze stáhnout šablonu (layout)',
'unable to download plugin: %s': 'nelze stáhnout plugin: %s',
'Unable to download the list of plugins': 'Nelze stáhnout seznam pluginů',
'unable to install plugin "%s"': 'nelze instalovat plugin "%s"',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'unable to uninstall "%s"': 'nelze instalovat "%s"',
'unable to upgrade because "%s"': 'nelze upgradovat, protože "%s"',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'Unsupported webserver working mode: %s': 'Nepodporovaný mód webového serveru: %s',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'update all languages': 'aktualizovat všechny jazyky o nové texty ze zdrojových souborů',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgrade now',
'upgrade now to %s': 'upgrade now to %s',
'upgrade now': 'upgradovat nyní',
'upgrade now to %s': 'upgradovat nyní na %s',
'upload': 'nahrát',
'Upload': 'Upload',
'Upload': 'Upload (nahrát)',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User': 'Uživatel',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
@@ -451,30 +672,45 @@
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'variables',
'Users': 'Uživatelé',
'Using the shell may lock the database to other users of this app.': 'Použití příkazového shellu může uzamknout databázi ostatním uživatelům této aplikace.',
'variables': 'proměnné',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Web Framework',
'View': 'Šablona (View)',
'Views': 'Šablony (Views)',
'views': 'šablony (views)',
'Warning!': 'Pozor!',
'WARNING:': 'POZOR:',
'WARNING: The following views could not be compiled:': 'POZOR: Následující šablony se nepodařilo zkompilovat:',
'Web Framework': 'Webový framework',
'web2py Admin Password': 'web2py Heslo administrátora',
'web2py apps to deploy': 'web2py aplikace k nasazení',
'web2py Debugger': 'web2py Debugger',
'web2py downgrade': 'web2py downgrade',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'web2py Recent Tweets': 'Nedávné tweety na Twitteru o web2py',
'web2py upgrade': 'aktualizace Web2py',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve web2py',
'Welcome to web2py!': 'Vítejte ve web2py!',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci.',
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.',
'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'WSGI reference name': 'jméno WSGI reference',
'YES': 'ANO',
'Yes': 'Ano',
'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can inspect variables using the console below': 'You can inspect variables using the console below',
'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'You have one more login attempt before you are locked out': 'Máte jen jeden další pokus k přihlášení před zablokováním',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You only need these if you have already registered': 'Toto potřebujete jen tehdy, jestliže jste se už registroval(a)',
'You visited the url %s': 'Navštívili jste stránku %s,',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
}
-579
View File
@@ -1,579 +0,0 @@
/*=============================================================
GENERAL
==============================================================*/
html,body{height:auto;background:transparent;}
/*=============================================================
CONTROLS
==============================================================*/
label,
input,
button,
select,
textarea,
button.btn
{
font-size:13px;
font-weight:normal;
line-height:18px;
}
textarea,
select
{
margin-bottom:9px;
}
select,
/*textarea,*/
input[type="text"],
input[type="password"],
input[type="datetime"],
input[type="datetime-local"],
input[type="date"],
input[type="month"],
input[type="time"],
input[type="week"],
input[type="number"],
input[type="email"],
input[type="url"],
input[type="search"],
input[type="tel"],
input[type="color"],
.uneditable-input,
a.btn-lnk
{
height:18px;
padding:4px;
font-size:13px;
line-height:18px;
}
.design h3,
.plugin h3
{
background-position:0 2px;
}
select,
input[type="file"]
{
height:28px;
line-height:28px;
}
input[type="submit"],
input[type="button"]
{
font-size:13px;
height:28px;
line-height:18px;
padding:4px 10px;
}
input[type="radio"],
input[type="checkbox"]
{
margin-top:2px;
}
.button.btn
{
line-height:1.25em;
font-size:inherit;
border:none;
text-shadow:none;
margin-bottom:0px;
-webkit-border-radius:0px;
-moz-border-radius:0px;
border-radius:0px;
-webkit-box-shadow:none;
-moz-box-shadow:none;
box-shadow:none);
}
.button.btn:hover
{
background-color:transparent;
-webkit-transition: background-position 0s linear;
-moz-transition: background-position 0s linear;
-o-transition: background-position 0s linear;
transition: background-position 0s linear;
}
form label
{
font-weight:bold;
}
.help
{
border-color:transparent;
}
/* tree menu */
.folder
{
border:none;
}
.folder>i
{
display:none;
}
.celled
{
padding-top: 2px;
}
.celled-one
{
padding-top: 1px;
}
.test h3
{
border:0;
padding-left:18px;
}
/*=============================================================
FLASH MESSAGEBOX
==============================================================*/
.flash
{
position:fixed;
width:50%;
top:49px;
left:25%;
right:25%;
cursor:default;
text-align:center;
padding:8px 35px 8px 14px;
z-index:5620;
}
.flash>.close
{
color:inherit;
opacity:0.7;
}
.flash>.close:hover
{
opacity:0.9;
}
/*=============================================================
NAVBAR
==============================================================*/
.navbar-fixed-top .navbar-inner,
.navbar-static-top .navbar-inner
{
/* in place of shadow image */
-webkit-box-shadow:0px 10px 20px rgba(195,195,195,1.0);
-moz-box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
box-shadow: 0px 10px 20px rgba(195,195,195,1.0);
//zoom:1; /* IE6-9 */
filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=0, OffY=10, Color=#000000); /* IE6-9 */
padding:0;
}
.navbar-inverse .navbar-inner
{
min-height:33px; /* required - override */
height:33px;
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
background:#292929 url(../images/header_bg.png) repeat-x;
border:none;
}
#header
{
background:transparent;
}
#header.navbar
{
overflow:visible;
}
.navbar-inverse .nav > li > a
{
padding:0;
line-height:1.25;
text-shadow:none;
}
.navbar .btn-navbar
{
padding:4px;
margin:5px 5px 0 5px;
}
#menu{margin-right:-7px;}
/*=============================================================
FOOTER
==============================================================*/
#footer
{
padding-bottom:0;
}
/*=============================================================
MAIN
==============================================================*/
#main
{
position:static;
padding-top:0;
padding-bottom:0;
}
/*=============================================================
SIDEBAR
==============================================================*/
.sidebar_inner
{
background:transparent;
padding:0;
min-width:auto;
}
.sidebar .box {
border-top:1px solid #EEE;
}
/*=============================================================
WIZARD
==============================================================*/
.step div.help li
{
line-height:inherit;
}
.ms-container .ms-selectable li.ms-elem-selectable,
.ms-container .ms-selection li.ms-elem-selected
{
font-size:13px;
}
.input-append a.btn
{
padding:4px;
height:18px;
font-size:13px;
line-height:18px;
}
/*=============================================================
ERRORS TABLE
==============================================================*/
.errors .table th
{
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
}
.tablebar span.help
{
font-weight:normal;
line-height:1.25em;
text-shadow:none;
width:auto;
}
/*=============================================================
TOOLTIP
==============================================================*/
.tooltip.in
{
opacity:1;
filter:alpha(opacity=100);
}
.tooltip-inner
{
opacity:1;
text-align:left;
background:#9fb364;
color:#eef1d9;
border:1px solid #eef1d9;
font-style:italic;
padding:0.3em;
-moz-border-radius:0.5em;
border-radius:0.5em;
font-size:13px;
text-transform:none;
}
.tooltip.right .tooltip-arrow,
.tooltip.left .tooltip-arrow
{
border-color:transparent;
}
/*=============================================================
THE GRID
==============================================================*/
.w2p_grid_bottom_bar .w2p_export_menu
{
line-height:18px;
margin-left:0;
}
.w2p_export_menu .dropdown-toggle
{
cursor:pointer;
margin:0;
padding:0;
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(white), to(#E6E6E6));
background-image: -webkit-linear-gradient(top, white, #E6E6E6);
background-image: -o-linear-gradient(top, white, #E6E6E6);
background-image: linear-gradient(to bottom, white, #E6E6E6);
background-image: -moz-linear-gradient(top, white, #E6E6E6);
}
.w2p_export_menu ul
{
margin-top:2px;
display:none;
}
.w2p_export_menu li
{
display:list-item;
margin:0;
}
div.web2py_grid
{
font-size:13px;
line-height:18px;
}
.web2py_grid a.btn
{
font-size:13px;
line-height:18px;
padding:4px 10px;
margin-left:0;
margin-right:4px;
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
}
.web2py_grid .input-append .btn
{
padding:4px 10px;
margin-right:0;
font-family:inherit;
color:#333;
text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);
border:1px solid #c5c5c5;
}
.web2py_grid select:focus
{
border-color:rgba(232,149,60,0.8);
outline:0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(232,149,60,0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 8px rgba(232, 149, 60, 0.6);
}
.web2py_console input[type="button"],
.web2py_grid .row_buttons a.btn
{
color:#333;
line-height:18px;
padding:4px 10px;
text-shadow:rgba(255, 255, 255, 0.74902) 0px 1px 1px;
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.web2py_console input[type="button"]:hover,
.web2py_grid .row_buttons a.btn:hover
{
color:#333;
border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
background:#E6E6E6;
background-position: 0 -15px !important;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
.web2py_table
{
border:none;
}
.web2py_table table
{
/*table-layout:fixed;*/
margin-bottom:4px;
}
.web2py_table table td
{
/*word-wrap:break-word;*/ /*uncomment when "table-layout:fixed" is applied */
}
.web2py_grid thead th
{
background-color:transparent;
padding:4px 5px;
line-height:18px;
vertical-align:bottom;
border-right:0;
border-bottom:0;
word-wrap:break-word;
}
.web2py_grid .btn-group > .dropdown-menu
{
font-size:13px;
}
.web2py_grid .dropdown-menu li > a:hover,
.web2py_grid .dropdown-menu li > a:focus
{
filter:progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
background-image:none;
background-color:#E8953C;
}
.pagination
{
margin:0;
height:30px;
}
.pagination ul > li > a
{
line-height:28px;
}
#w2p_grid_addbtn:focus,
#w2p_search-form :focus,
.btn:focus
{
outline:none;
}
.web2py_console input[type="button"]:focus,
.web2py_grid .row_buttons a.btn:focus
{
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
}
div.web2py_counter.span6
{
min-height:20px;
}
.web2py_paginator
{
border:0;
margin:0;
padding:0;
background-color:transparent;
}
.web2py_paginator ul li a
{
margin-right:0;
padding:0 14px;
border:1px solid #DDD;
border-left-width:0;
color:#E8953C;
}
.web2py_paginator ul li a:hover
{
background: whiteSmoke;
border: 1px solid #DDD;
border-left-width:0;
color:#e2821b;
}
.web2py_paginator ul li:first-child a,
.web2py_paginator ul li:first-child a:hover
{
border-left-width:1px;
}
.web2py_paginator .current
{
font-weight:normal;
}
.web2py_paginator ul li.current a:hover
{
color:#999;
}
.editor-bar-column a[name="save"]
{
background-color: whiteSmoke;
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
background-image: -o-linear-gradient(top,white,#E6E6E6);
background-image: linear-gradient(to bottom,white,#E6E6E6);
background-image: -moz-linear-gradient(top,white,#E6E6E6);
background-repeat: repeat-x;
padding:2px 6px;
font-size:11px;
line-height:17px;
margin:0;
}
.editor-bar-column a[name="save"]:hover
{
background-color: #E6E6E6;
background-position: 0 -15px;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
.keybindings
{
padding:0 18px 10px;
}
.keybindings li
{
margin-bottom:0;
}
/*----- translate page ---*/
.languageform input
{
margin-bottom:0;
}
.languageform div
{
margin-bottom:9px;
}
.languageform input.untranslated
{
background-color:#FC0;
}
.step #wizard_nav .first-box
{
padding-top:0;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media (max-width: 979px)
{
/*-----------------------------------
Navbar
-------------------------------------*/
#header .navbar-inner
{
padding:0;
}
/*collapsed menu*/
.navbar .nav-collapse .nav
{
background:#222;
padding:8px 2px 8px 8px;
-webkit-border-bottom-right-radius:8px;
-webkit-border-bottom-left-radius:8px;
-moz-border-radius-bottomright:8px;
-moz-border-radius-bottomleft:8px;
border-bottom-right-radius:8px;
border-bottom-left-radius:8px;
}
#menu
{
margin-right:0;
}
#menu li
{
float:none;
}
#menu a.button,
#menu a.button span
{
background-image:url(../images/menu_responsive.png);
}
#menu a.button
{
padding:0 1em 0 0;
}
}
@media(max-width:632px)
{
/*-----------------------------------
footer
-------------------------------------*/
#footer
{
height:auto;
}
#footer select
{
margin-top:8px;
}
}
+1 -1
View File
@@ -476,7 +476,7 @@ h4.editableapp { background: #fff url(../images/folder.png) no-repeat; }
h4.currentapp { background: #fff url(../images/folder_locked.png) no-repeat; }
.flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; }
.w2p_flash { position:fixed; width:50%; top:49px; left:25%; right:25%; cursor:default; text-align:center; z-index:5620; }
span#closeflash {position:absolute; top:1px; right:-1px; font-size:150%; border:1px solid black; border-color: transparent transparent #fbeed5 #fbeed5; border-radius: 0 0 0 4px; width:22px; }
span#closeflash:hover {font-weight:bold; cursor:pointer; }
-322
View File
@@ -1,322 +0,0 @@
/** these MUST stay **/
a {text-decoration:none; white-space:nowrap}
a:hover {text-decoration:underline}
a.button {text-decoration:none}
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
font-family:Helvetica}
h1 {font-size:4.00em}
h2 {font-size:3.00em}
h3 {font-size:2.00em}
h4 {font-size:1.50em}
h5 {font-size:1.25em}
h6 {font-size:1.12em}
th,label {font-weight:bold; white-space:nowrap;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
form table tr td label {text-align:left}
p,table,ol,ul {padding:0; margin: 0.75em 0}
p {text-align:justify}
ol, ul {list-style-position:outside; margin-left:2em}
li {margin-bottom:0.5em}
span,input,select,textarea,button,label,a {display:inline}
img {border:0}
blockquote,blockquote p,p blockquote {
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
i,em {font-style:italic}
strong {font-weight:bold}
small {font-size:0.8em}
code {font-family:Courier}
textarea {width:100%}
video {width:400px}
audio {width:200px}
[type="text"], [type="password"], select {
margin-right: 5px; width: 300px;
}
.hidden {display:none;visibility:visible}
.right {float:right; text-align:right}
.left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle}
/** end **/
/* Sticky footer begin */
.main {
padding:20px 0 50px 0;
}
.footer,.push {
height:6em;
padding:1em 0;
clear:both;
}
.footer-content {position:relative; bottom:-4em; width:100%}
.auth_navbar {
white-space:nowrap;
}
/* Sticky footer end */
.footer {
border-top:1px #DEDEDE solid;
}
.header {
/* background:<fill here for header image>; */
}
fieldset {padding:16px; border-top:1px #DEDEDE solid}
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
/* fix ie problem with menu */
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
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, 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}
/* Fix */
#auth_user_remember__row label {display:inline}
#web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/
div.flash {
font-weight:bold;
display:none;
position:fixed;
padding:10px;
top:48px;
right:250px;
min-width:280px;
opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle;
cursor:pointer;
color:#fff;
background-color:#000;
border:2px solid #fff;
border-radius:8px;
-o-border-radius: 8px;
-moz-border-radius:8px;
-webkit-border-radius:8px;
background-image: -webkit-linear-gradient(top,#222,#000);
background-image: -o-linear-gradient(top,#222,#000);
background-image: -moz-linear-gradient(90deg, #222, #000);
background-image: linear-gradient(top,#222,#000);
background-repeat: repeat-x;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
z-index:2000;
}
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
.ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.flash:hover { opacity:0.25; }
div.error_wrapper {display:block}
div.error {
width: 298px;
background:red;
border: 2px solid #d00;
color:white;
padding:5px;
display:inline-block;
background-image: -webkit-linear-gradient(left,#f00,#fdd);
background-image: -o-linear-gradient(left,#f00,#fdd);
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
background-image: linear-gradient(left,#f00,#fdd);
background-repeat: repeat-y;
}
.topbar {
padding:10px 0;
width:100%;
color:#959595;
vertical-align:middle;
padding:auto;
background-image:-khtml-gradient(linear,left top,left bottom,from(#333333),to(#222222));
background-image:-moz-linear-gradient(top,#333333,#222222);
background-image:-ms-linear-gradient(top,#333333,#222222);
background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#333333),color-stop(100%,#222222));
background-image:-webkit-linear-gradient(top,#333333,#222222);
background-image:-o-linear-gradient(top,#333333,#222222);
background-image:linear-gradient(top,#333333,#222222);
filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333',endColorstr='#222222',GradientType=0);
-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
-moz-box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
box-shadow:0 1px 3px rgba(0,0,0,0.25),inset 0 -1px 0 rgba(0,0,0,0.1);
}
.topbar a {
color:#e1e1e1;
}
#navbar {float:right; padding:5px; /* same as superfish */}
.statusbar {
background-color:#F5F5F5;
margin-top:1em;
margin-bottom:1em;
padding:.5em 1em;
border:1px solid #ddd;
border-radius:5px;
-moz-border-radius:5px;
-webkit-border-radius:5px;
}
.breadcrumbs {float:left}
.copyright {float:left}
#poweredBy {float:right}
/* #MEDIA QUERIES SECTION */
/*
*Grid
*
* The default style for SQLFORM.grid even using jquery-iu or another ui framework
* will look better with the declarations below
* if needed to remove base.css consider keeping these following lines in some css file.
*/
/* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
padding:10px 5px 10px 5px;
}
.web2py_grid tr.odd {background-color:#F9F9F9}
.web2py_grid tr:hover {background-color:#F5F5F5}
/*
.web2py_breadcrumbs a {
line-height:20px; margin-right:5px; display:inline-block;
padding:3px 5px 3px 5px;
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
color:#3C3C3D;
text-shadow:1px 1px 0 #FFFFFF;
white-space:nowrap; overflow:visible; cursor:pointer;
background:#ECECEC;
border:1px solid #CACACA;
-webkit-border-radius:2px; -moz-border-radius:2px;
-webkit-background-clip:padding-box; border-radius:2px;
outline:none; position:relative; zoom:1; *display:inline;
}
*/
.web2py_console form {
width: 100%;
display: inline;
vertical-align: middle;
margin: 0 0 0 5px;
}
.web2py_console form select {
margin:0;
}
.web2py_search_actions {
float:left;
text-align:left;
}
.web2py_grid .row_buttons {
min-height:25px;
vertical-align:middle;
}
.web2py_grid .row_buttons a {
margin:3px;
}
.web2py_search_actions {
width:100%;
}
.web2py_grid .row_buttons a,
.web2py_paginator ul li a,
.web2py_search_actions a,
.web2py_console input[type=submit],
.web2py_console input[type=button],
.web2py_console button {
line-height:20px;
margin-right:2px; display:inline-block;
padding:3px 5px 3px 5px;
}
.web2py_counter {
margin-top:5px;
margin-right:2px;
width:35%;
float:right;
text-align:right;
}
/*Fix firefox problem*/
.web2py_table {clear:both; display:block}
.web2py_paginator {
padding:5px;
text-align:right;
background-color:#f2f2f2;
}
.web2py_paginator ul {
list-style-type:none;
margin:0px;
padding:0px;
}
.web2py_paginator ul li {
display:inline;
}
.web2py_paginator .current {
font-weight:bold;
}
.web2py_breadcrumbs ul {
list-style:none;
margin-bottom:18px;
}
li.w2p_grid_breadcrumb_elem {
display:inline-block;
}
.web2py_console form { vertical-align: middle; }
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#wiki_page_body {
width: 600px;
height: auto;
min-height: 400px;
}
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
@@ -1,264 +0,0 @@
/*=============================================================
CUSTOM RULES
==============================================================*/
body{height:auto;} /* to avoid vertical scroll bar */
a{}
a:visited{}
a:hover{}
a:focus{}
a:active{}
h1{}
h2{}
h3{}
h4{}
h5{}
h6{}
div.flash.flash-center{left:25%;right:25%;}
div.flash.flash-top,div.flash.flash-top:hover{
position:relative;
display:block;
margin:0;
padding:1em;
top:0;
left:0;
width:100%;
text-align:center;
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
color:#865100;
background:#feea9a;
border:1px solid;
border-top:0px;
border-left:0px;
border-right:0px;
border-radius:0;
opacity:1;
}
#header{margin-top:60px;}
.mastheader h1 {
margin-bottom:9px;
font-size:81px;
font-weight:bold;
letter-spacing:-1px;
line-height:1;
font-size:54px;
}
.mastheader small {
font-size:20px;
font-weight:300;
}
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
.auth_navbar a{white-space:nowrap;} /* to avoid the nav split on more lines */
.auth_navbar a:hover{color:white;text-decoration:none;}
ul#navbar>.auth_navbar{
display:inline-block;
padding:5px;
}
/* form errors message box customization */
div.error_wrapper{margin-bottom:9px;}
div.error_wrapper .error{
border-radius: 4px;
-o-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
/* below rules are only for formstyle = bootstrap
trying to make errors look like bootstrap ones */
div.controls .error_wrapper{
display:inline-block;
margin-bottom:0;
vertical-align:middle;
}
div.controls .error{
min-width:5px;
background:inherit;
color:#B94A48;
border:none;
padding:0;
margin:0;
/*display:inline;*/ /* uncommenting this, the animation effect is lost */
}
div.controls .help-inline{color:#3A87AD;}
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
div.controls select +.error_wrapper {margin-left:5px;}
.ie-lte7 div.error{color:#fff;}
/* beautify brand */
.navbar {margin-bottom:0}
.navbar-inverse .brand{color:#c6cecc;}
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
.navbar-inverse .brand b>span{font-size:22px;color:white}
.navbar-inverse .brand:hover b>span{color:white}
/* beautify web2py link in navbar */
span.highlighted{color:#d8d800;}
.open span.highlighted{color:#ffff00;}
/*=============================================================
OVERRIDING WEB2PY.CSS RULES
==============================================================*/
/* reset to default */
a{white-space:normal;}
li{margin-bottom:0;}
textarea,button{display:block;}
/*reset ul padding */
ul#navbar{padding:0;}
/* label aligned to related input */
td.w2p_fl,td.w2p_fc {padding:0;}
#web2py_user_form td{vertical-align:middle;}
/*=============================================================
OVERRIDING BOOTSTRAP.CSS RULES
==============================================================*/
/* because web2py handles this via js */
textarea { width:90%}
.hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{
background-image:url("../images/glyphicons-halflings.png")
}
/* right folder for bootstrap white images/icons */
.icon-white,
.nav-tabs > .active > a > [class^="icon-"],
.nav-tabs > .active > a > [class*=" icon-"],
.nav-pills > .active > a > [class^="icon-"],
.nav-pills > .active > a > [class*=" icon-"],
.nav-list > .active > a > [class^="icon-"],
.nav-list > .active > a > [class*=" icon-"],
.navbar-inverse .nav > .active > a > [class^="icon-"],
.navbar-inverse .nav > .active > a > [class*=" icon-"],
.dropdown-menu > li > a:hover > [class^="icon-"],
.dropdown-menu > li > a:hover > [class*=" icon-"],
.dropdown-menu > .active > a > [class^="icon-"],
.dropdown-menu > .active > a > [class*=" icon-"] {
background-image:url("../images/glyphicons-halflings-white.png");
}
/* bootstrap has a label as input's wrapper while web2py has a div */
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
/* bootstrap has button instead of input */
input[type="button"], input[type="submit"]{margin-right:8px;}
/* web2py radio widget adjustment */
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
/*=============================================================
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
==============================================================*/
/*when formstyle=table3cols*/
tr#auth_user_remember__row>td.w2p_fw>div{padding-bottom:8px;}
td.w2p_fw div>label{vertical-align:middle;}
td.w2p_fc {padding-bottom:5px;}
/*when formstyle=divs*/
div#auth_user_remember__row{margin-top:4px;}
div#auth_user_remember__row>.w2p_fl{display:none;}
div#auth_user_remember__row>.w2p_fw{min-height:39px;}
div.w2p_fw,div.w2p_fc{
display:inline-block;
vertical-align:middle;
margin-bottom:0;
}
div.w2p_fc{
padding-left:5px;
margin-top:-8px;
}
/*when formstyle=ul*/
form>ul{
list-style:none;
margin:0;
}
li#auth_user_remember__row{margin-top:4px;}
li#auth_user_remember__row>.w2p_fl{display:none;}
li#auth_user_remember__row>.w2p_fw{min-height:39px;}
/*when formstyle=bootstrap*/
#auth_user_remember__row label.checkbox{display:block;}
span.inline-help{display:inline-block;}
input[type="text"].input-xlarge,input[type="password"].input-xlarge{width:270px;}
/*when recaptcha is used*/
#recaptcha{min-height:30px;display:inline-block;margin-bottom:0;line-height:30px;vertical-align:middle;}
td>#recaptcha{margin-bottom:6px;}
div>#recaptcha{margin-bottom:9px;}
div.control-group.error{
width:auto;
background:transparent;
border:0;
color:inherit;
padding:0;
background-repeat:repeat;
}
/*=============================================================
OTHER RULES
==============================================================*/
/* Massimo Di Pierro fixed alignment in forms with list:string */
form table tr{margin-bottom:9px;}
td.w2p_fw ul{margin-left:0px;}
/* web2py_console in grid and smartgrid */
.hidden{visibility:visible;}
.web2py_console input{
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
.web2py_console input[type="submit"],
.web2py_console input[type="button"],
.web2py_console button{
padding-top:4px;
padding-bottom:4px;
margin:3px 0 0 2px;
}
.web2py_console a,
.web2py_console select,
.web2py_console input
{
margin:3px 0 0 2px;
}
.web2py_grid form table{width:auto;}
/* auth_user_remember checkbox extrapadding in IE fix */
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
div.controls .error {
width: auto;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}
#navbar{/*top:5px;*/}
div.flash{right:5px;}
.dropdown-menu ul{visibility:visible;}
}
@media only screen and (max-width:479px){
body{
padding-left:10px;
padding-right:10px;
}
.navbar-fixed-top,.navbar-fixed-bottom {
margin-left:-10px;
margin-right:-10px;
}
input[type="text"],input[type="password"],select{
width:95%;
}
}
@media (max-width: 767px) {
.navbar {
margin-right: -20px;
margin-left: -20px;
}
}
@@ -1,122 +0,0 @@
/*=============================================================
BOOTSTRAP DROPDOWN MENU
==============================================================*/
.dropdown-menu ul{
left:100%;
position:absolute;
top:0;
visibility:hidden;
margin-top:-1px;
}
.dropdown-menu li:hover ul{visibility:visible;}
.navbar .dropdown-menu ul:before{
border-bottom:7px solid transparent;
border-left:none;
border-right:7px solid rgba(0, 0, 0, 0.2);
border-top:7px solid transparent;
left:-7px;
top:5px;
}
.nav > li.dropdown > a:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #000000;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 2px;
margin-top: 8px;
border-bottom-color: #FFFFFF;
border-top-color: #FFFFFF;
}
.dropdown-menu span{display:inline-block;}
ul.dropdown-menu li.dropdown > a:after {
border-left: 4px solid #000;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 8px;
margin-top: 6px;
}
ul.nav li.dropdown:hover ul.dropdown-menu {
display: block;
}
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
/*=============================================================
BOOTSTRAP SUBMIT BUTTON
==============================================================*/
input[type='submit']:not(.btn) {
display: inline-block;
padding: 4px 14px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
color: #333;
text-align: center;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
vertical-align: middle;
cursor: pointer;
background-color: whiteSmoke;
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
background-image: -o-linear-gradient(top,white,#E6E6E6);
background-image: linear-gradient(to bottom,white,#E6E6E6);
background-image: -moz-linear-gradient(top,white,#E6E6E6);
background-repeat: repeat-x;
border: 1px solid #BBB;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
border-bottom-color: #A2A2A2;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
}
input[type='submit']:not(.btn):hover {
color: #333;
text-decoration: none;
background-color: #E6E6E6;
background-position: 0 -15px;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
background-color: #E6E6E6;
background-color: #D9D9D9 9;
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
}
/*=============================================================
OTHER
==============================================================*/
.ie-lte8 .navbar-fixed-top {position:static;}
+4 -4
View File
@@ -77,10 +77,10 @@ function doClickSave() {
t.attr('disabled', '');
var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) {
$('.flash').html(decodeURIComponent(flash))
$('.w2p_flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>')
.slideDown();
} else $('.flash').hide();
} else $('.w2p_flash').hide();
try {
if(json.error) {
window.location.href = json.redirect;
@@ -158,10 +158,10 @@ function doToggleBreakpoint(filename, url, sel) {
// show flash message (if any)
var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) {
$('.flash').html(decodeURIComponent(flash))
$('.w2p_flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>')
.slideDown();
} else $('.flash').hide();
} else $('.w2p_flash').hide();
try {
if(json.error) {
window.location.href = json.redirect;
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -477,11 +477,11 @@ function filter_files() {
message=data['message'];
for(var i=0; i<files.length; i++)
jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown();
jQuery('.flash').html(message).slideDown();
jQuery('.w2p_flash').html(message).slideDown();
});
} else {
jQuery('.component_contents li, .formfield, .comptools').slideDown();
jQuery('.flash').html('').hide();
jQuery('.w2p_flash').html('').hide();
}
}
jQuery(document).ready(function(){
+1 -1
View File
@@ -47,7 +47,7 @@
<div id="{{=globals().get('main_id', 'main')}}" class="container-fluid">
<div id="main_inner" class="row-fluid">
<div class="span12">
<div class="flash alert">{{=response.flash or ''}}</div>
<div class="w2p_flash alert">{{=response.flash or ''}}</div>
{{include}}
</div><!-- /main span12 -->
</div><!-- /main row-fluid -->
@@ -3,6 +3,7 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var w2p_ajax_disable_with_message = "{{=T('Working...')}}";
var ajax_error_500 = '{{=T.M('An error occured, please [[reload %s]] the page') % URL(args=request.args, vars=request.get_vars) }}'
//--></script>
{{
+2 -2
View File
@@ -10,7 +10,7 @@ session.forget()
cache_expire = not request.is_local and 300 or 0
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def index():
return response.render()
@@ -26,7 +26,7 @@ def what():
return response.render(images=images)
@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
#@cache.action(time_expire=300, cache_model=cache.ram, quick='P')
def download():
return response.render()
@@ -35,12 +35,6 @@ def hello6():
response.flash = 'Hello World in a flash!'
return dict(message=T('Hello World'))
def status():
""" page that shows internal status"""
return dict(toolbar=response.toolbar())
def redirectme():
""" redirects to /{{=request.application}}/{{=request.controller}}/hello3 """
@@ -27,4 +27,4 @@ def xml():
def beautify():
return dict(message=BEAUTIFY(request))
return dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')])))
+3 -1
View File
@@ -1 +1,3 @@
session.connect(request,response,cookie_key='yoursecret')
from gluon.utils import web2py_uuid
cookie_key = cache.ram('cookie_key',lambda: web2py_uuid(),None)
session.connect(request,response,cookie_key=cookie_key)
@@ -10,6 +10,7 @@
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]]
- [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[web2py Reference Project http://www.web2pyref.com/]]
- [[An advanced tutorial https://milesm.pythonanywhere.com/wiki]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!)
- [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
@@ -19,9 +20,9 @@
#### Code
- [[web2pyslices (recipes) http://www.web2pyslices.com popup]]
- [[Layouts http://www.web2py.com/layouts popup]]
- [[Dashboard welcome app https://github.com/mjbeller/web2py-starter]]
- [[stupid.css theme https://github.com/mdipierro/web2py-welcome-theme-stupid]]
- [[Plugins http://www.web2py.com/plugins popup]]
- [[More Plugins http://dev.s-cubism.com/web2py_plugins]]
- [[Appliances http://www.web2py.com/appliances popup]]
- [[web2py utils http://packages.python.org/web2py_utils/ popup]]
- [[Sublime text 3 plugin https://bitbucket.org/kfog/w2p popup]]
@@ -24,6 +24,10 @@ French speakers group
``web2py-fr``:groupdates
## Italian Group
- [[https://groups.google.com/forum/?fromgroups#!forum/web2py-it https://groups.google.com/forum/?fromgroups#!forum/web2py-it popup]]
## Japanese Group
Japanese speakers group
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -6
View File
@@ -1,7 +1,11 @@
.calendar{z-index:99;position:relative;display:none;background:#fff;border:2px solid #000;font-size:11px;color:#000;cursor:default;font-family:Arial,Helvetica,sans-serif;
border-radius: 10px;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
}.calendar table{margin:0px;font-size:11px;color:#000;cursor:default;font-family:tahoma,verdana,sans-serif;}.calendar .button{text-align:center;padding:1px;color:#fff;background:#000;}.calendar .nav{background:#000;color:#fff}.calendar thead .title{font-weight:bold;padding:1px;background:#000;color:#fff;text-align:center;}.calendar thead .name{padding:2px;text-align:center;background:#bbb;}.calendar thead .weekend{color:#f00;}.calendar thead .hilite {background-color:#666;}.calendar thead .active{padding:2px 0 0 2px;background-color:#c4c0b8;}.calendar tbody .day{width:2em;text-align:right;padding:2px 4px 2px 2px;}.calendar tbody .day.othermonth{color:#aaa;}.calendar tbody .day.othermonth.oweekend{color:#faa;}.calendar table .wn{padding:2px 3px 2px 2px;background:#bbb;}.calendar tbody .rowhilite td{background:#ddd;}.calendar tbody td.hilite{background:#bbb;}.calendar tbody td.active{background:#bbb;}.calendar tbody td.selected{font-weight:bold;background:#ddd;}.calendar tbody td.weekend{color:#f00;}.calendar tbody td.today{font-weight:bold;color:#00f;}.calendar tbody .disabled{color:#999;}.calendar tbody .emptycell{visibility:hidden;}.calendar tbody .emptyrow{display:none;}.calendar tfoot .ttip{background:#bbb;padding:1px;background:#000;color:#fff;text-align:center;}.calendar tfoot .hilite{background:#ddd;}.calendar tfoot .active{}.calendar .combo{position:absolute;display:none;width:4em;top:0;left:0;cursor:default;background:#e4e0d8;padding:1px;z-index:100;}.calendar .combo .label,.calendar .combo .label-IEfix{text-align:center;padding:1px;}.calendar .combo .label-IEfix{width:4em;}.calendar .combo .active{background:#c4c0b8;}.calendar .combo .hilite{background:#048;color:#fea;}.calendar td.time{padding:1px 0;text-align:center;background-color:#bbb;}.calendar td.time .hour,.calendar td.time .minute,.calendar td.time .ampm{padding:0 3px 0 4px;font-weight:bold;}.calendar td.time .ampm{text-align:center;}.calendar td.time .colon{padding:0 2px 0 3px;font-weight:bold;}.calendar td.time span.hilite{}.calendar td.time span.active{border-color:#f00;background-color:#000;color:#0f0;}.hour,.minute{font-size:2em;}
.calendar {z-index:2000;position:relative;margin-top:140px;display:none;background-color:white;border:1px solid #000;color:#000;cursor:default;box-shadow:0 0 10px #666}.calendar * {text-align: center;font-size:10px!important}
.calendar table {border-collapse:collapse}
.calendar tbody tr:hover {background-color:#fbf6d9}
.calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
.calendar thead tr {background-color:#f1f1f1}
.calendar tbody tr {border-bottom:2px solid #f1f1f1}
.calendar th {font-weight:string; padding:5px; vertical-align:bottom; text-align:left}
.calendar thead th {vertical-align:bottom}
.calendar tbody th {vertical-align:top}
#CP_hourcont{z-index:99;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:99;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:99;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:99}
#CP_hourcont{z-index:2000;padding:0;position:absolute;border:1px dashed #666;background-color:#eee;display:none;}#CP_minutecont{z-index:2000;background-color:#ddd;padding:1px;position:absolute;width:45px;display:none;}.floatleft{float:left;}.CP_hour{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:35px;}.CP_minute{z-index:2000;padding:1px;font-family:Arial,Helvetica,sans-serif;font-size:9px;white-space:nowrap;cursor:pointer;width:auto;}.CP_over{background-color:#fff;z-index:2000}
@@ -1,20 +0,0 @@
@import url(http://fonts.googleapis.com/css?family=Economica);
@@import url(http://fonts.googleapis.com/css?family=Belleza);
body { font-family: Arial, Helvetica; }
a, a:visited, a:hover, h1,h2,h3,h4,h5 {color: #658883}
a.btn-danger, a.btn-warning, a.btn-success {color:white}
h1,h2,h3,h4,h5 { font-family: "Economica", Arial, Helevtica; }
body {
background: url('../images/stripes.png') repeat-x;
}
#header {
margin-top: 40px;
}
.btn-180 {
width: 180px;
}
.page-header {
border-bottom: 0;
}
+359
View File
@@ -0,0 +1,359 @@
/************
Created by Massimo Di Pierro
Stupid.css is what the names says, take it with a grain of salt
License: BSD
************/
/*** basic styles ***/
*, *:after, *:before {border:0; margin:0; padding:0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
html, body {max-width: 100vw !important; overflow-x: hidden !important}
body {font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif}
p, li {margin-bottom:0.5em}
p {text-align:justify}
label, strong {font-weight:bold}
ul {list-style-type:none; padding-left:20px}
a {text-decoration:none; color:#26a69a; white-space:nowrap}
a:hover {cursor:pointer}
h1,h2,h3,h4,h5,h6{font-weight:bold; text-transform:uppercase}
h1{font-size:4em; margin:1.0em 0 0.25em 0}
h2{font-size:2.4em; margin:0.9em 0 0.25em 0}
h3{font-size:1.8em; margin:0.8em 0 0.25em 0}
h4{font-size:1.6em; margin:0.7em 0 0.25em 0}
h5{font-size:1.4em; margin:0.6em 0 0.25em 0}
h6{font-size:1.2em; margin:0.5em 0 0.25em 0}
table {border-collapse:collapse}
tbody tr:hover {background-color:#fbf6d9}
td, th {padding:5px; vertical-align:top; text-align:left; border:0}
thead tr {background-color:#f1f1f1}
tbody tr {border-bottom:2px solid #f1f1f1}
th {font-weight:bold; padding:5px; vertical-align:bottom; text-align:left}
thead th {vertical-align:bottom}
tbody th {vertical-align:top}
header, footer {with:100%}
@media (max-width:599px) {
h1{font-size:2em}
h2{font-size:1.8em}
h3{font-size:1.6em}
h4{font-size:1.4em}
h5{font-size:1.2em}
h6{font-size:1.0em}
}
/*** buttons ***/
.btn, button, [type=button], [type=submit] {padding:0.5em 1em !important; margin:0 0.5em 0.5em 0; display:inline-block; background-color:#26a69a; color:white}
.btn:hover, button:hover, [type=button]:hover, [type=submit]:hover {box-shadow:0 0 10px #666; text-decoration:none; cursor:pointer}
.btn.small, table .btn {padding:0.25em 0.5em !important; font-size:0.8em}
.btn.large {padding:1em 2em !important; font-size:1.2em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important}
.center {text-align:center !important; margin-left:auto; margin-right:auto}
.center>div {text-align:left}
.right {right:0; text-align:right}
.middle div {vertical-align:middle !important}
.bottom div {vertical-align:bottom !important}
.xscroll {overflow-x:scroll}
.yscroll {overflow-y:scroll}
.nowrap {white-space:nowrap; overflow-x:hidden}
.fill {width:100%}
.lifted {box-shadow:5px 5px 10px #666}
.relative {position:relative}
.relative>div {position:absolute}
.spaced {margin-bottom:20px; margin-top:20px}
.hidden {display:none}
/*** forms ***/
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]), [type=file]:before {outline:none; padding:0.5em 1em; margin:0.5px; border-bottom:1px solid #ddd; width:100%}
textarea {width:100%; border:1px solid #ddd; padding:4px 8px; outline:none; outline:none}
select {-webkit-appearance:none; outline:none; padding:0.5em 1em; border-radius:0; margin:0.5px; border-bottom:1px solid #ddd; width:100%;background-color:transparent}
input, textarea, select, button {font-size:12px}
input:not([type]):hover, input:not([type=checkbox]):not([type=radio]):not([type=button]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
input:invalid, input.error {background:#cc1f00!important;color:white}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding: 0 20px 0 0; vertical-align:top}
.container>.fill {width:100%; margin-right:-20px}
.container img, .container video {max-width:100%}
@media (min-width:800px) {
.max900 {max-width:900px; margin-left:auto; margin-right:auto}
.quarter {width:25%; margin-right:-5px}
.half {width:50%; margin-right:-10px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
.threequarters {width:75%; margin-right:-15px}
}
@media (min-width:600px) and (max-width:799px) {
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.threequarters.compressible {width:75%; margin-right:-15px}
.quarter:not(.compressible), .half:not(.compressible), .threequarters:not(.compressible) {width:100%; margin-right:-20px}
.third {width:33.33%; margin-right:-6.66px}
.twothirds {width:66.66%; margin-right:-13.33px}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right {float:left; text-align:left}
}
@media (max-width:599px) {
.quarter:not(.compressible), .half:not(.compressible), .third:not(.compressible), .twothirds:not(.compressible), .threequarters:not(.compressible) {width:100%;}
label.quarter:not(.compressible).right, label.half:not(.compressible).right, label.threequarters:not(.compressible).right,
label.third:not(.compressible).right, label.twothirds:not(.compressible).right {float:left; text-align:left}
.quarter.compressible {width:25%; margin-right:-5px}
.half.compressible {width:50%; margin-right:-10px}
.third.compressible {width:33.33%; margin-right:-6.66px}
.twothirds.compressible {width:66.66%; margin-right:-13.33px}
.threequarters.compressible {width:75%; margin-right:-15px}
}
/*** progress bar from http://codepen.io/holdencreative/details/pvxGxy ***/
.progress {
margin-left:-15px;
margin-right:-15px;
position:relative;
height:8px;
display:block;
width:120%;
background-color:#acece6;
border-radius:0 !important;
background-clip:padding-box;
overflow:hidden;
}
.progress .determinate {
position:absolute;
background-color:inherit;
top:0;
bottom:0;
background-color:#26a69a;
transition:width .3s linear;
}
.progress .indeterminate {
background-color:#26a69a;
}
.progress .indeterminate:before {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
}
.progress .indeterminate:after {
content:'';
position:absolute;
background-color:inherit;
top:0;
left:0;
bottom:0;
will-change:left, right;
animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;
animation-delay:1.15s;
}
@-webkit-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-moz-keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@keyframes indeterminate {
0% {left:-35%; right:100%}
60% {left:100%; right:-90%}
100% {left:100%; right:-90%}
}
@-webkit-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@-moz-keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
@keyframes indeterminate-short {
0% {left:-200%; right:100%}
60% {left:107%; right:-8%}
100% {left:107%; right:-8%}
}
/**** dropdown menu from http://codepen.io/philhoyt/pen/ujHzd ***/
.menu {list-style:none; position:relative; margin:0; padding:0}
.menu.right {float:right}
.menu a {padding:0 15px; text-decoration:none;text-align:left;font-family:"HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; text-align:left}
.menu li {position:relative; float:left; margin:0; padding:0}
.menu ul {background:white; border:1px solid #e1e1e1; visibility:hidden; opacity:0; position:absolute; top:110%; padding:0; z-index:1000; transition:all 0.2s ease-out; list-style-type:none; box-shadow:5px 5px 10px #666}
.menu ul a {padding:10px 15px; color:#333; font-weight:700; font-size:12px; line-height:16px; display: block}
.menu ul li {float:none; width:200px}
.menu ul ul {top:0; left:80%; z-index:1100}
.menu li:hover > ul {visibility:visible; opacity:1}
.menu>li>ul>li:first-child:before{content:''; position:absolute; width:1px; height:1px; border:10px solid transparent; left:50px; top:-20px; margin-left:-10px; border-bottom-color:white}
.menu.dark ul {background:black; border:1px solid black}
.menu.dark ul a {color:white}
.menu.dark>li>ul>li:first-child:before{border-bottom-color:black}
@media (max-width:599px) {
header .menu li, header .menu ul {width: 100%}
header .menu.right {float:left; text-align:left}
header .menu ul ul {top:2.5em; left:-1px}
}
@media (min-width:600px) {
.ham {display:none!important}
.burger.accordion * {max-height:1000px; overflow:visible}
}
/*** pulsating ring from https://jsfiddle.net/mandynicole/7xrKP/ *******/
.pulse:after {
content:"";
border:3px solid #00e6ac;
-webkit-border-radius:30px;
height:40px;
width:40px;
position:absolute;
margin-left:-20px;
margin-top:-20px;
-webkit-animation:pulsate 1s ease-out;
-webkit-animation-iteration-count:infinite;
opacity:0.0
}
@-webkit-keyframes pulsate {
0% {-webkit-transform:scale(0.1, 0.1); opacity:0.0}
50% {opacity:1.0}
100% {-webkit-transform:scale(1.2, 1.2); opacity:0.0}
}
/**** underline effect ***/
a:not(.btn):not(.noeffect) {position:relative}
a:not(.btn):not(.noeffect):hover {color:#26a69a}
a:not(.btn):not(.noeffect):hover:after {width:100%}
a:not(.btn):not(.noeffect):after {
display:block;
position:absolute;
left:0;
bottom:-1px;
width:0;
height:2px;
background-color:#26a69a;
content:"";
transition:width 0.2s;
}
/**** modal ***/
.modal {
position:fixed;
z-index:9999;
top:0;
bottom:0;
left:0;
right:0;
background-color:rgba(0,0,0,0.8);
padding-top:20vh;
transition:opacity 500ms;
visibility:hidden;
opacity:0;
}
.modal:target {visibility:visible; opacity:1}
.modal div {margin-left:auto; margin-right:auto}
.modal .close:not(.btn) {position:absolute; top:10px; right:10px; font-size:20px}
.modal .close {transition:all 200ms}
/*** tooltips from http://codepen.io/trezy/pen/Khnzy ***/
[data-tooltip] {position:relative}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:hover:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
left:0;
margin-top:12px;
z-index:2000;
}
[data-tooltip]:hover:after {
z-index:2000;
background-color:rgba(0,0,0,0.8);
border:4px solid rgba(0,0,0,0.8);
border-radius:7px;
color:white;
text-transform:none;
font-size: 12px;
content:attr(data-tooltip);
left:0;
top:2px;
margin-left:-20;
margin-top:1.5em;
padding:5px 15px;
white-space:pre-wrap;
width:100px;
}
/*** accordion ***/
.accordion>input ~ label:before {content:"▲ "; color:#ddd}
.accordion>input:checked ~ label:before {content:"▼ "; color:#ddd}
.accordion>input {display:none}
.accordion>input:checked ~ *:not(label) {
max-height: 1000px !important;
overflow:visible !important;
-webkit-transition: max-height .3s ease-in;
transition: max-height .3s ease-in;
}
.accordion>*:not(label) {
max-height: 0;
overflow: hidden;
margin: 0;
padding: 0;
-webkit-transition: max-height .3s ease-out;
transition: max-height .3s ease-out;
}
/*** cards from http://codepen.io/edeesims/pen/iGDzk ***/
.card {perspective: 500px; max-width:100%}
.card>div {
position: absolute;
width: 100%;
height: 100%;
box-shadow: 0 0 15px rgba(0,0,0,0.1);
transition: transform 1s;
transform-style: preserve-3d;
}
.card:hover>div {
transform: rotateY( 180deg ) ;
transition: transform 0.5s;
}
.card>div>div {
position: absolute;
height: 100%;
width: 100%;
backface-visibility: hidden;
}
.card>div>div:nth-child(2) {
transform: rotateY( 180deg );
}
/*** colors from http://clrs.cc/ ***/
.navy{background-color:#001f3f!important;color:white}.blue{background-color:#0074d9!important;color:white}.aqua{background-color:#7fdbff!important;color:black}.teal{background-color:#39cccc!important;color:white}.olive{background-color:#3d9970!important;color:white}.green{background-color:#2ecc40!important;color:white}.aquamarine{background-color:#26a69a!important;color:white}.lime{background-color:#01ff70!important;color:black}.yellow{background-color:#ffdc00!important;color:black}.orange{background-color:#ff851b!important;color:white}.red{background-color:#cc1f00!important;color:white}.fuchsia{background-color:#f012be!important;color:white}.pink{background-color:#ee6e73!important;color:white}.purple{background-color:#b10dc9!important;color:white}.maroon{background-color:#85144b!important;color:white}.white{background-color:#fff!important;color:black;-webkit-box-shadow:inset 0px 0px 0px 1px #ddd;-moz-box-shadow:inset 0px 0px 0px 1px #ddd;box-shadow:inset 0px 0px 0px 1px #ddd}.gray{background-color:#aaa!important;color:white}.silver{background-color:#f1f1f1!important;color:black}.black{background-color:#000!important;color:white}.glass{background:rgba(255,255,255,0.5)!important;color:black}
/**** tags ****/
.tags > span {
padding: 4px 9px;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
font-size:12px;
margin: 5px 5px 5px 0 !important;
line-height: 32px;
}
.tags.dismissible > span:hover {opacity: 0.5}
.tags.dismissible > span:not(.off):after {content:" ✕"}
.tags > span.off {background-color: #ccc}
.tags.dismissible > span.off:hover {background-color:#26a69a}
+20 -145
View File
@@ -1,84 +1,17 @@
/** these MUST stay **/
a {text-decoration:none; white-space:nowrap}
a:hover {text-decoration:underline}
a.button {text-decoration:none}
h1,h2,h3,h4,h5,h6 {margin:0.5em 0 0.25em 0; display:block;
font-family:Helvetica}
h1 {font-size:4.00em}
h2 {font-size:3.00em}
h3 {font-size:2.00em}
h4 {font-size:1.50em}
h5 {font-size:1.25em}
h6 {font-size:1.12em}
th,label {font-weight:bold; white-space:nowrap;}
td,th {text-align:left; padding:2px 5px 2px 5px}
th {vertical-align:middle; border-right:1px solid white}
td {vertical-align:top}
form table tr td label {text-align:left}
p,table,ol,ul {padding:0; margin: 0.75em 0}
p {text-align:justify}
ol, ul {list-style-position:outside; margin-left:2em}
li {margin-bottom:0.5em}
span,input,select,textarea,button,label,a {display:inline}
img {border:0}
blockquote,blockquote p,p blockquote {
font-style:italic; margin:0.5em 30px 0.5em 30px; font-size:0.9em}
i,em {font-style:italic}
strong {font-weight:bold}
small {font-size:0.8em}
code {font-family:Courier}
textarea {width:100%}
video {width:400px}
audio {width:200px}
[type="text"], [type="password"], select {
margin-right: 5px; width: 300px;
}
.hidden {display:none;visibility:visible}
header a {color: white; font-size:1.1em}
main {min-height: 70vh}
.form-group {padding-bottom: 10px !important;}
.w2p_hidden {display:none;visibility:visible}
.right {float:right; text-align:right}
.left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle}
/** end **/
/* Sticky footer begin */
.main {
padding:20px 0 50px 0;
}
.footer,.push {
height:6em;
padding:1em 0;
clear:both;
}
.footer-content {position:relative; bottom:-4em; width:100%}
.auth_navbar {
white-space:nowrap;
}
/* Sticky footer end */
.footer {
border-top:1px #DEDEDE solid;
}
.header {
/* background:<fill here for header image>; */
}
fieldset {padding:16px; border-top:1px #DEDEDE solid}
fieldset legend {text-transform:uppercase; font-weight:bold; padding:4px 16px 4px 16px; background:#f1f1f1}
/* fix ie problem with menu */
td.w2p_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
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, 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;}
div.w2p_export_menu {white-space: wrap; margin:5px 0}
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; font-size:0.7em; color: black}
/* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em}
@@ -88,54 +21,30 @@ div.w2p_export_menu a, div.w2p_wiki_tags a, div.w2p_cloud a {margin-left:5px; pa
#web2py_user_form td {vertical-align:top}
/*********** web2py specific ***********/
div.flash {
div.w2p_flash {
font-weight:bold;
display:none;
position:fixed;
padding:10px;
top:48px;
right:250px;
min-width:280px;
padding:20px 20px 20px 50px;
width:100%;
opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle;
cursor:pointer;
color:#fff;
background-color:#000;
border:2px solid #fff;
border-radius:8px;
-o-border-radius: 8px;
-moz-border-radius:8px;
-webkit-border-radius:8px;
background-image: -webkit-linear-gradient(top,#222,#000);
background-image: -o-linear-gradient(top,#222,#000);
background-image: -moz-linear-gradient(90deg, #222, #000);
background-image: linear-gradient(top,#222,#000);
background-repeat: repeat-x;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
color:#000;
background-color:#ffdc00;
z-index:2000;
}
div.flash #closeflash{color:inherit; float:right; margin-left:15px;}
div.w2p_flash:before{content:"×";float:right; margin-right:100px; color:black;}
.ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.flash:hover { opacity:0.25; }
div.w2p_flash:hover { opacity:0.80; }
div.error_wrapper {display:block}
div.error {
width: 298px;
background:red;
border: 2px solid #d00;
color:white;
color:red;
padding:5px;
display:inline-block;
background-image: -webkit-linear-gradient(left,#f00,#fdd);
background-image: -o-linear-gradient(left,#f00,#fdd);
background-image: -moz-linear-gradient(0deg, #f00, #fdd);
background-image: linear-gradient(left,#f00,#fdd);
background-repeat: repeat-y;
}
.topbar {
@@ -190,34 +99,8 @@ div.error {
*/
/* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
padding:10px 5px 10px 5px;
}
.web2py_grid tr.odd {background-color:#F9F9F9}
.web2py_grid tr:hover {background-color:#F5F5F5}
/*
.web2py_breadcrumbs a {
line-height:20px; margin-right:5px; display:inline-block;
padding:3px 5px 3px 5px;
font-family:'lucida grande',tahoma,verdana,arial,sans-serif;
color:#3C3C3D;
text-shadow:1px 1px 0 #FFFFFF;
white-space:nowrap; overflow:visible; cursor:pointer;
background:#ECECEC;
border:1px solid #CACACA;
-webkit-border-radius:2px; -moz-border-radius:2px;
-webkit-background-clip:padding-box; border-radius:2px;
outline:none; position:relative; zoom:1; *display:inline;
}
*/
.web2py_grid td {color: black;}
.web2py_console form {
width: 100%;
@@ -302,11 +185,6 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#wiki_page_body {
width: 600px;
@@ -317,13 +195,10 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2}
.ie-lte8 div.flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.flash:hover {filter:alpha(opacity=25);}
.ie-lte8 div.w2p_flash{ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0 ); }
.ie-lte8 div.w2p_flash:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px}
.control-label.readonly{
padding-top:0px !important;
padding-right:0px !important;
}
.web2py_console .form-control {width: 20%; display: inline;}
.web2py_console #w2p_keywords {width: 50%;}
.web2py_search_actions a, .web2py_console input[type=submit], .web2py_console input[type=button], .web2py_console button { padding: 6px 12px; }
@@ -1,264 +0,0 @@
/*=============================================================
CUSTOM RULES
==============================================================*/
body{height:auto;} /* to avoid vertical scroll bar */
a{}
a:visited{}
a:hover{}
a:focus{}
a:active{}
h1{}
h2{}
h3{}
h4{}
h5{}
h6{}
div.flash.flash-center{left:25%;right:25%;}
div.flash.flash-top,div.flash.flash-top:hover{
position:relative;
display:block;
margin:0;
padding:1em;
top:0;
left:0;
width:100%;
text-align:center;
text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);
color:#865100;
background:#feea9a;
border:1px solid;
border-top:0px;
border-left:0px;
border-right:0px;
border-radius:0;
opacity:1;
}
#header{margin-top:60px;}
.mastheader h1 {
margin-bottom:9px;
font-size:81px;
font-weight:bold;
letter-spacing:-1px;
line-height:1;
font-size:54px;
}
.mastheader small {
font-size:20px;
font-weight:300;
}
/* auth navbar - primitive style */
.auth_navbar,.auth_navbar a{color:inherit;}
.navbar-inner {-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.ie-lte7 .auth_navbar,.auth_navbar a{color:expression(this.parentNode.currentStyle['color']); /* ie7 doesn't support inherit */}
.auth_navbar a{white-space:nowrap;} /* to avoid the nav split on more lines */
.auth_navbar a:hover{color:white;text-decoration:none;}
ul#navbar>.auth_navbar{
display:inline-block;
padding:5px;
}
/* form errors message box customization */
div.error_wrapper{margin-bottom:9px;}
div.error_wrapper .error{
border-radius: 4px;
-o-border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
}
/* below rules are only for formstyle = bootstrap
trying to make errors look like bootstrap ones */
div.controls .error_wrapper{
display:inline-block;
margin-bottom:0;
vertical-align:middle;
}
div.controls .error{
min-width:5px;
background:inherit;
color:#B94A48;
border:none;
padding:0;
margin:0;
/*display:inline;*/ /* uncommenting this, the animation effect is lost */
}
div.controls .help-inline{color:#3A87AD;}
div.controls .error_wrapper +.help-inline {margin-left:-99999px;}
div.controls select +.error_wrapper {margin-left:5px;}
.ie-lte7 div.error{color:#fff;}
/* beautify brand */
.navbar {margin-bottom:0}
.navbar-inverse .brand{color:#c6cecc;}
.navbar-inverse .brand b{display:inline-block;margin-top:-1px;}
.navbar-inverse .brand b>span{font-size:22px;color:white}
.navbar-inverse .brand:hover b>span{color:white}
/* beautify web2py link in navbar */
span.highlighted{color:#d8d800;}
.open span.highlighted{color:#ffff00;}
/*=============================================================
OVERRIDING WEB2PY.CSS RULES
==============================================================*/
/* reset to default */
a{white-space:normal;}
li{margin-bottom:0;}
textarea,button{display:block;}
/*reset ul padding */
ul#navbar{padding:0;}
/* label aligned to related input */
td.w2p_fl,td.w2p_fc {padding:0;}
#web2py_user_form td{vertical-align:middle;}
/*=============================================================
OVERRIDING BOOTSTRAP.CSS RULES
==============================================================*/
/* because web2py handles this via js */
textarea { width:90%}
.hidden{visibility:visible;}
/* right folder for bootstrap black images/icons */
[class^="icon-"],[class*=" icon-"]{
background-image:url("../images/glyphicons-halflings.png")
}
/* right folder for bootstrap white images/icons */
.icon-white,
.nav-tabs > .active > a > [class^="icon-"],
.nav-tabs > .active > a > [class*=" icon-"],
.nav-pills > .active > a > [class^="icon-"],
.nav-pills > .active > a > [class*=" icon-"],
.nav-list > .active > a > [class^="icon-"],
.nav-list > .active > a > [class*=" icon-"],
.navbar-inverse .nav > .active > a > [class^="icon-"],
.navbar-inverse .nav > .active > a > [class*=" icon-"],
.dropdown-menu > li > a:hover > [class^="icon-"],
.dropdown-menu > li > a:hover > [class*=" icon-"],
.dropdown-menu > .active > a > [class^="icon-"],
.dropdown-menu > .active > a > [class*=" icon-"] {
background-image:url("../images/glyphicons-halflings-white.png");
}
/* bootstrap has a label as input's wrapper while web2py has a div */
div>input[type="radio"],div>input[type="checkbox"]{margin:0;}
/* bootstrap has button instead of input */
input[type="button"], input[type="submit"]{margin-right:8px;}
/* web2py radio widget adjustment */
.generic-widget input[type='radio'] {margin:-1px 0 0 0; vertical-align: middle;}
.generic-widget input[type='radio'] + label {display:inline-block; margin:0 0 0 6px; vertical-align: middle;}
/*=============================================================
RULES FOR SOLVING CONFLICTS BETWEEN WEB2PY.CSS AND BOOTSTRAP.CSS
==============================================================*/
/*when formstyle=table3cols*/
tr#auth_user_remember__row>td.w2p_fw>div{padding-bottom:8px;}
td.w2p_fw div>label{vertical-align:middle;}
td.w2p_fc {padding-bottom:5px;}
/*when formstyle=divs*/
div#auth_user_remember__row{margin-top:4px;}
div#auth_user_remember__row>.w2p_fl{display:none;}
div#auth_user_remember__row>.w2p_fw{min-height:39px;}
div.w2p_fw,div.w2p_fc{
display:inline-block;
vertical-align:middle;
margin-bottom:0;
}
div.w2p_fc{
padding-left:5px;
margin-top:-8px;
}
/*when formstyle=ul*/
form>ul{
list-style:none;
margin:0;
}
li#auth_user_remember__row{margin-top:4px;}
li#auth_user_remember__row>.w2p_fl{display:none;}
li#auth_user_remember__row>.w2p_fw{min-height:39px;}
/*when formstyle=bootstrap*/
#auth_user_remember__row label.checkbox{display:block;}
span.inline-help{display:inline-block;}
input[type="text"].input-xlarge,input[type="password"].input-xlarge{width:270px;}
/*when recaptcha is used*/
#recaptcha{min-height:30px;display:inline-block;margin-bottom:0;line-height:30px;vertical-align:middle;}
td>#recaptcha{margin-bottom:6px;}
div>#recaptcha{margin-bottom:9px;}
div.control-group.error{
width:auto;
background:transparent;
border:0;
color:inherit;
padding:0;
background-repeat:repeat;
}
/*=============================================================
OTHER RULES
==============================================================*/
/* Massimo Di Pierro fixed alignment in forms with list:string */
form table tr{margin-bottom:9px;}
td.w2p_fw ul{margin-left:0px;}
/* web2py_console in grid and smartgrid */
.hidden{visibility:visible;}
.web2py_console input{
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
.web2py_console input[type="submit"],
.web2py_console input[type="button"],
.web2py_console button{
padding-top:4px;
padding-bottom:4px;
margin:3px 0 0 2px;
}
.web2py_console a,
.web2py_console select,
.web2py_console input
{
margin:3px 0 0 2px;
}
.web2py_grid form table{width:auto;}
/* auth_user_remember checkbox extrapadding in IE fix */
.ie-lte9 input#auth_user_remember.checkbox {padding-left:0;}
div.controls .error {
width: auto;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media only screen and (max-width:979px){
body{padding-top:0px;}
#navbar{/*top:5px;*/}
div.flash{right:5px;}
.dropdown-menu ul{visibility:visible;}
}
@media only screen and (max-width:479px){
body{
padding-left:10px;
padding-right:10px;
}
.navbar-fixed-top,.navbar-fixed-bottom {
margin-left:-10px;
margin-right:-10px;
}
input[type="text"],input[type="password"],select{
width:95%;
}
}
@media (max-width: 767px) {
.navbar {
margin-right: -20px;
margin-left: -20px;
}
}
@@ -1,122 +0,0 @@
/*=============================================================
BOOTSTRAP DROPDOWN MENU
==============================================================*/
.dropdown-menu ul{
left:100%;
position:absolute;
top:0;
visibility:hidden;
margin-top:-1px;
}
.dropdown-menu li:hover ul{visibility:visible;}
.navbar .dropdown-menu ul:before{
border-bottom:7px solid transparent;
border-left:none;
border-right:7px solid rgba(0, 0, 0, 0.2);
border-top:7px solid transparent;
left:-7px;
top:5px;
}
.nav > li.dropdown > a:after {
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid #000000;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 2px;
margin-top: 8px;
border-bottom-color: #FFFFFF;
border-top-color: #FFFFFF;
}
.dropdown-menu span{display:inline-block;}
ul.dropdown-menu li.dropdown > a:after {
border-left: 4px solid #000;
border-right: 4px solid transparent;
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
content: "";
display: inline-block;
height: 0;
opacity: 0.7;
vertical-align: top;
width: 0;
margin-left: 8px;
margin-top: 6px;
}
ul.nav li.dropdown:hover ul.dropdown-menu {
display: block;
}
.open >.dropdown-menu ul{display:block;} /* fix menu issue when BS2.0.4 is applied */
/*=============================================================
BOOTSTRAP SUBMIT BUTTON
==============================================================*/
input[type='submit']:not(.btn) {
display: inline-block;
padding: 4px 14px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
color: #333;
text-align: center;
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
vertical-align: middle;
cursor: pointer;
background-color: whiteSmoke;
background-image: -webkit-gradient(linear,0 0,0 100%,from(white),to(#E6E6E6));
background-image: -webkit-linear-gradient(top,white,#E6E6E6);
background-image: -o-linear-gradient(top,white,#E6E6E6);
background-image: linear-gradient(to bottom,white,#E6E6E6);
background-image: -moz-linear-gradient(top,white,#E6E6E6);
background-repeat: repeat-x;
border: 1px solid #BBB;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
border-bottom-color: #A2A2A2;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);
filter: progid:dximagetransform.microsoft.gradient(enabled=false);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);
}
input[type='submit']:not(.btn):hover {
color: #333;
text-decoration: none;
background-color: #E6E6E6;
background-position: 0 -15px;
-webkit-transition: background-position .1s linear;
-moz-transition: background-position .1s linear;
-o-transition: background-position .1s linear;
transition: background-position .1s linear;
}
input[type='submit']:not(.btn).active, input[type='submit']:not(.btn):active {
background-color: #E6E6E6;
background-color: #D9D9D9 9;
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
-moz-box-shadow: inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),0 1px 2px rgba(0, 0, 0, 0.05);
}
/*=============================================================
OTHER
==============================================================*/
.ie-lte8 .navbar-fixed-top {position:static;}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -1,33 +0,0 @@
// this code improves bootstrap menus and adds dropdown support
jQuery(function(){
jQuery('.nav>li>a').each(function(){
if(jQuery(this).parent().find('ul').length)
jQuery(this).attr({'class':'dropdown-toggle','data-toggle':'dropdown'}).append('<b class="caret"></b>');
});
jQuery('.nav li li').each(function(){
if(jQuery(this).find('ul').length)
jQuery(this).addClass('dropdown-submenu');
});
function adjust_height_of_collapsed_nav() {
var cn = jQuery('div.collapse');
if (cn.get(0)) {
var cnh = cn.get(0).style.height;
if (cnh>'0px'){
cn.css('height','auto');
}
}
}
function hoverMenu(){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
var mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
var mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
}
hoverMenu(); // first page load
jQuery(window).resize(hoverMenu); // on resize event
jQuery('ul.nav li.dropdown a').click(function(){window.location=jQuery(this).attr('href');});
});
@@ -1,14 +1,13 @@
{{extend 'layout.html'}}
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
<div class="contentleft">
<div >
{{=get_content('main')}}
</div>
{{=get_content('official')}}
{{=get_content('community')}}
{{=get_content('more')}}
<div>
{{=get_content('main')}}
<center>
<iframe src="//player.vimeo.com/hubnut/album/3016728?color=ff6600&amp;background=ffffff&amp;slideshow=1&amp;video_title=1&amp;video_byline=1" width="400" height="300" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>
</center>
{{=get_content('official')}}
{{=get_content('community')}}
{{=get_content('more')}}
</div>
@@ -5,33 +5,59 @@
<h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2>
<center style="padding:20px">
<table class="downloads">
<tr>
<th>For Normal Users</th>
<th>For Testers</th>
<th>For Developers</th>
</tr>
<tr>
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a></td>
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a></td>
<td><a class="btn btn-180 btn-danger" href="http://github.com/web2py/web2py/">Git Repository</a></td>
</tr>
<tr>
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a></td>
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a></td>
<td></td>
</tr>
<tr>
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a></td>
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a></td>
<td><a class="btn btn-180 btn-danger" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a></td>
</tr>
<tr>
<td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td>
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/releases">Change Log</a></td>
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/issues">Report a Bug</a></td>
</tr>
<center class="spaced">
<table class="twothirds">
<thead>
<tr>
<th>For Normal Users</th>
<th>For Testers</th>
<th>For Developers</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
</td>
<td>
<a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
</td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_osx.zip">For Mac</a>
</td>
<td></td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a>
</td>
<td>
<a class="btn btn180 rounded red" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a>
</td>
</tr>
<tr>
<td>
<a class="btn btn180 rounded red" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/releases">Change Log</a>
</td>
<td>
<a class="btn btn180 rounded" href="https://github.com/web2py/web2py/issues">Report a Bug</a>
</td>
</tr>
</tbody>
</table>
</center>
@@ -32,7 +32,7 @@ def hello1():
return "Hello World"
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello1">hello1</a></p>
<p>If the controller function returns a string, that is the body of the rendered page.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello1">hello1</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -40,7 +40,7 @@ def hello2():
return T("Hello World")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello2">hello2</a></p>
<p>The function T() marks strings that need to be translated. Translation dictionaries can be created at /admin/default/design<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello2">hello2</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -51,7 +51,7 @@ def hello3():
<b>and view: simple_examples/hello3.html</b>
{{=CODE(open(os.path.join(request.folder,'views/simple_examples/hello3.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>If you return a dictionary, the variables defined in the dictionery are visible to the view (template).
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello3.html">hello3</a></p>
<p>Actions can also be be rendered in other formsts like JSON, <a href="/{{=request.application}}/simple_examples/hello3.json">hello3.json</a>, and XML, <a href="/{{=request.application}}/simple_examples/hello3.xml">hello3.xml</a></p>
@@ -62,7 +62,7 @@ def hello4():
return dict(message=T("Hello World"))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can change the view, but the default is /[controller]/[function].html. If the default is not found web2py tries to render the page using the generic.html view.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello4">hello4</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello4">hello4</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -76,7 +76,7 @@ def hello5():
<li>named arguments and name starts with '_'. These are mapped blindly into tag attributes and the '_' is removed. attributes without value like "READONLY" can be created with the argument "_readonly=ON".</li>
<li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later.
</ul>
<p>Try it here: <a href="/{{=request.application}}/simple_examples/hello5">hello5</a></p>
<p>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello5">hello5</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -86,7 +86,7 @@ def hello6():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>response.flash allows you to flash a message to the user when the page is returned. Use session.flash instead of response.flash to display a message after redirection. With default layout, you can click on the flash to make it disappear.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/hello6">hello6</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/hello6">hello6</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -94,7 +94,6 @@ def status():
return dict(toobar=response.toolbar())
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here we are showing the request, session and response objects using the generic.html template.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/status">status</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -102,7 +101,7 @@ def redirectme():
redirect(URL('hello3'))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can do redirect.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/redirectme">redirectme</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -110,7 +109,7 @@ def raisehttp():
raise HTTP(400,"internal error")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can raise HTTP exceptions to return an error page.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/raisehttp">raisehttp</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -128,7 +127,7 @@ def servejs():
return 'alert("This is a Javascript document, it is not supposed to run!");'
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can serve other than HTML pages by changing the contenttype via the response.headers. The gluon.contenttype module can help you figure the type of the file to be served. NOTICE: this is not necessary for static files unless you want to require authorization.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/servejs">servejs</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/servejs">servejs</a></p>
<h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -136,7 +135,7 @@ def servejs():
return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}])
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>If you are into Ajax, web2py includes gluon.contrib.<a href="http://cheeseshop.python.org/pypi/simplejson">simplejson</a>, developed by Bob Ippolito. This module provides a fast and easy way to serve asynchronous content to your Ajax page. gluon.simplesjson.dumps(...) can serialize most Python types into <a href="http://www.json.org">JSON</a>. gluon.contrib.simplejson.loads(...) performs the reverse operation.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/makejson">makejson</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makejson">makejson</a></p>
<p>New in web2py 1.63: Any normal action returning a dict is automatically serialized in JSON if '.json' is appended to the URL.</p>
@@ -152,7 +151,7 @@ def makertf():
response.headers['Content-Type']='text/rtf'
return q.dumps(doc)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/makertf">makertf</a></p>
<p>web2py also includes gluon.contrib.<a href="http://pyrtf.sourceforge.net/">pyrtf</a>, developed by Simon Cusack and revised by Grant Edwards. This module allows you to generate Rich Text Format documents including colored formatted text and pictures.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/makertf">makertf</a></p>
<h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE("""
@@ -179,7 +178,7 @@ def rss_aggregator():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>web2py includes gluon.contrib.<a href="http://www.dalkescientific.com/Python/PyRSS2Gen.html">rss2</a>, developed by Dalke Scientific Software, which generates RSS2 feeds, and
gluon.contrib.<a href="http://www.feedparser.org/">feedparser</a>, developed by Mark Pilgrim, which collects RSS and ATOM feeds. The above controller collects a slashdot feed and makes new one.
<br/>Try it here: <a href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/rss_aggregator">rss_aggregator</a></p>
<h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
@@ -194,7 +193,7 @@ def ajaxwiki_onclick():
return MARKMIN(request.vars.text).xml()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>.
web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p>
web2py also includes gluon.contrib.<a href="http://code.google.com/p/python-markdown2/">markdown</a>.WIKI helper (markdown2) which converts WIKI markup to HTML following <a href="http://en.wikipedia.org/wiki/Markdown">this syntax</a>. In this example we added a fancy ajax effect.<br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/ajaxwiki">ajaxwiki</a></p>
<h2 id="session_examples">Session Examples</h2>
@@ -207,7 +206,7 @@ def counter():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b>
{{=CODE(open(os.path.join(request.folder,'views/session_examples/counter.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Click to count. The session.counter is persistent for this user and application. Every applicaiton within the system has its own separate session management.
<br/>Try it here: <a href="/{{=request.application}}/session_examples/counter">counter</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/session_examples/counter">counter</a></p>
<h2 id="template_examples">Template Examples</h2>
@@ -219,7 +218,7 @@ def variables():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/variables.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>A view (also known as template) is just an HTML file with &#123;&#123;...&#125;&#125; tags. You can put ANY python code into the tags, no need to indent but you must use pass to close blocks. The view is transformed into a python code and then executed. &#123;&#123;=a&#125;&#125; prints a.xml() or escape(str(a)).
<br/>Try it here: <a href="/{{=request.application}}/template_examples/variables">variables</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/variables">variables</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -228,7 +227,7 @@ def test_for():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_for.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do for and while loops.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_for">test_for</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_for">test_for</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -237,7 +236,7 @@ def test_if():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_if.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do if, elif, else.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_if">test_if</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_if">test_if</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -246,7 +245,7 @@ def test_try():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_try.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can do try, except, finally.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_try">test_try</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_try">test_try</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -255,7 +254,7 @@ def test_def():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/test_def.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can write functions in HTML too.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/test_def">test_def</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/test_def">test_def</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -264,7 +263,7 @@ def escape():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/escape.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>The argument of &#123;&#123;=...&#125;&#125; is always escaped unless it is an object with a .xml() method such as link, A(...), a FORM(...), a XML(...) block, etc.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/escape">escape</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/escape">escape</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
@@ -273,16 +272,16 @@ def xml():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/xml.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>If you do not want to escape the argument of &#123;&#123;=...&#125;&#125; mark it as XML.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/xml">xml</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/xml">xml</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE("""
def beautify():
return dict(message=BEAUTIFY(request))
dict(message=BEAUTIFY(dict(a=1,b=[2,3,dict(hello='world')])))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b>
{{=CODE(open(os.path.join(request.folder,'views/template_examples/beautify.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can use BEAUTIFY to turn lists and dictionaries into organized HTML.
<br/>Try it here: <a href="/{{=request.application}}/template_examples/beautify">beautify</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/template_examples/beautify">beautify</a></p>
<h2 id="layout_examples">Layout Examples</h2>
@@ -298,7 +297,7 @@ def civilized():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/civilized.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>You can specify the layout file at the top of your view. civilized Layout file is a view that somewhere in the body contains &#123;&#123;include&#125;&#125;.
<br/>Try it here: <a href="/{{=request.application}}/layout_examples/civilized">civilized</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/civilized">civilized</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE("""
@@ -310,7 +309,7 @@ def slick():
return dict(message="you clicked on slick")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/slick.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/slick.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Same here, but using a different template.<br/>Try it here: <a href="/{{=request.application}}/layout_examples/slick">slick</a></p>
<p>Same here, but using a different template.<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/slick">slick</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE("""
@@ -323,7 +322,7 @@ def basic():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b>
{{=CODE(open(os.path.join(request.folder,'views/layout_examples/basic.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>'layout.html' is the default template, every application has a copy of it.
<br/>Try it here: <a href="/{{=request.application}}/layout_examples/basic">basic</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/layout_examples/basic">basic</a></p>
<h2 id="form_examples">Form Examples</h2>
@@ -347,7 +346,7 @@ def form():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can use HTML helpers like FORM, INPUT, TEXTAREA, OPTION, SELECT to build forms. The "value=" attribute sets the initial value of the field (works for TEXTAREA and OPTION/SELECT too) and the requires attribute sets the validators.
FORM.accepts(..) tries to validate the form and, on success, stores vars into form.vars. On failure the error messages are stored into form.errors and shown in the form.
<br/>Try it here: <a href="/{{=request.application}}/form_examples/form">form</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/form_examples/form">form</a></p>
<h2 id="database_examples">Database Examples</h2>
@@ -497,7 +496,7 @@ def cache_in_ram():
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached in ram for 5 seconds. The string 'time' is used as cache key.
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram">cache_in_ram</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -508,7 +507,7 @@ def cache_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) for 5 seconds.
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_on_disk">cache_on_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE("""
@@ -519,7 +518,7 @@ def cache_in_ram_and_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The output of <tt>lambda:time.ctime()</tt> is cached on disk (using the shelve module) and then in ram for 5 seconds. web2py looks in ram first and if not there it looks on disk. If it is not on disk it calls the function. This is useful in a multiprocess type of environment. The two times do not have to be the same.
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_in_ram_and_disk">cache_in_ram_and_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -530,7 +529,7 @@ def cache_in_ram_and_disk():
t=time.ctime()
return dict(time=t,link=A('click to reload',_href=URL(r=request)))""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here the entire controller (dictionary) is cached in ram for 5 seconds. The result of a select cannot be cached unless it is first serialized into a table <tt>lambda:SQLTABLE(db().select(db.user.ALL)).xml()</tt>. You can read below for an even better way to do it.
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_in_ram">cache_controller_in_ram</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE("""
@@ -541,7 +540,7 @@ def cache_controller_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Here the entire controller (dictionary) is cached on disk for 5 seconds. This will not work if the dictionary contains unpickleable objects.
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_on_disk">cache_controller_on_disk</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE("""
@@ -553,7 +552,7 @@ def cache_controller_and_view():
return response.render(d)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p><tt>response.render(d)</tt> renders the dictionary inside the controller, so everything is cached now for 5 seconds. This is best and fastest way of caching!
<br/>Try it here: <a href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/cache_examples/cache_controller_and_view">cache_controller_and_view</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE("""
@@ -583,7 +582,7 @@ def data():
<b>In view: ajax_examples/index.html</b>
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/index.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>The javascript function "ajax" is provided in "web2py_ajax.html" and included by "layout.html". It takes three arguments, a url, a list of ids and a target id. When called, it sends to the url (via a get) the values of the ids and display the response in the value (of innerHTML) of the target id.
<br/>Try it here: <a href="/{{=request.application}}/ajax_examples/index">index</a></p>
<br/>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/index">index</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE("""
@@ -591,7 +590,7 @@ def flash():
response.flash='this text should appear!'
return dict()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>Try it here: <a href="/{{=request.application}}/ajax_examples/flash">flash</a></p>
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/flash">flash</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE("""
@@ -600,7 +599,7 @@ def fade():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<b>In view: ajax_examples/fade.html </b><br/>
{{=CODE(open(os.path.join(request.folder,'views/ajax_examples/fade.html'),'r').read(),language='html',link=URL('global','vars'),_class='boxCode')}}
<p>Try it here: <a href="/{{=request.application}}/ajax_examples/fade">fade</a></p>
<p>Try it here: <a class="btn" href="/{{=request.application}}/ajax_examples/fade">fade</a></p>
<h3>Excel-like spreadsheet via Ajax</h3>
Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms
+34 -48
View File
@@ -1,21 +1,8 @@
{{extend 'layout.html'}}
{{
import random
quotes = [
("web2py was the life saver today for me, my blog post: Standalone Usage of web2py's", "caglartoklu", "http://twitter.com/#!/caglartoklu/status/84292131707031553"),
("Get Things Done - Faster, Better and More Easily with web2py",
"Bruno Rocha", "http://twitter.com/#!/rochacbruno/status/73583156044890112"),
("Please use www.web2py.com when using MVC , no PHP/SQL stuff please...its 2011 not 1999", "rabblesoft", "http://twitter.com/#!/rabblesoft/status/79189028431343616"),
('web2py rules! as a sysadmin I like the no installation and no configuration approach a lot)', "kjogut", "http://twitter.com/#!/jkogut/status/61414554273447936"),
("web2py it is. Compatible with everything under the sun and great interfaces to googleappengine", "comamitc","http://twitter.com/#!/comamitc/status/51744719071477760"),
("If you are still learning python, web2py is best tool by far", "pbreit", "http://twitter.com/#!/pbreit/status/48260905775017984")
]
random.shuffle(quotes)
}}
<div class="row-fluid">
<div class="span12">
<div class="span8">
<div class="container">
<div class="twothirds">
<div class="padded">
<h3>web2py<sup>TM</sup> Web Framework</h3>
<p>Free open source full-stack framework for rapid development of fast, scalable, <a href="http://www.web2py.com/book/default/chapter/01#Security" target="_blank">secure</a> and portable database-driven web-based applications. Written and programmable in <a href="http://www.python.org" target="_blank">Python</a>.</p>
<table width="100%">
@@ -39,45 +26,44 @@ random.shuffle(quotes)
</table>
<p>Current version: <a href="{{=URL('download')}}">{{=request.env.web2py_version}} (<a href="http://www.gnu.org/licenses/lgpl.html">LGPLv3 License</a>)</p>
</div>
<div class="span4" style="text-align:center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23"><img src="{{=URL('static','images/infoworld2012.jpeg')}}" width="200px"/></a><br/>
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/>
<a class="btn btn-danger" href="https://www.pythonanywhere.com/try-web2py" style="margin-top:10px; width:180px; color:white">Try it now online</a><br/>
<a class="btn btn-danger" href="http://web2py.com/poweredby" style="margin-top:10px; width:180px; color:white">Sites Powered by web2py</a><br/><br/>
<a class="coinbase-button" data-code="df71ec5c2d5bc3b1c18139ab645f352b" data-button-style="donation_large" href="https://coinbase.com/checkouts/df71ec5c2d5bc3b1c18139ab645f352b">Donate Bitcoins</a><script src="https://coinbase.com/assets/button.js" type="text/javascript"></script>
</div>
<div class="third">
<div class="padded center">
<a href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<img src="{{=URL('static','images/infoworld2012.jpeg')}}">
</a>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now
</a>
<a class="btn rounded red fill" href="{{=URL('examples')}}">
Quick Examples
</a>
<a class="btn rounded red fill" href="https://www.pythonanywhere.com/try-web2py">
Try it now online
</a>
<a class="btn rounded red fill" href="http://web2py.com/poweredby">
Sites Powered by web2py
</a>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div class="span4">
<h3><a href="{{=URL('what')}}">Batteries Included</a></h3>
<div class="container">
<div class="third">
<div class="padded">
<h5><a href="{{=URL('what')}}">Batteries Included</a></h5>
<p>Everything you need in one package including fast multi-threaded web server, SQL database and web-based interface. No third party dependencies but works with <a href={{=URL('what')}}>third party tools</a>.</p>
</div>
<div class="span4">
<h3><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h3>
</div>
<div class="third">
<div class="padded">
<h5><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h5>
<p>Create, modify, deploy and manage application from anywhere using your browser. One web2py instance can run multiple web sites using different databases. Try the <a href="http://www.web2py.com/demo_admin">interactive demo</a>.</p>
</div>
<div class="span4">
<h3><a href="{{=URL('documentation')}}">Extensive Docs</a></h3>
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://dev.s-cubism.com/web2py_plugins" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
</div>
<div class="third">
<div class="padded">
<h5><a href="{{=URL('documentation')}}">Extensive Docs</a></h5>
<p>Start with some <a href="{{=URL('examples')}}">quick examples</a>, then read the <a href="http://www.web2py.com/book" target="_blank">manual</a> and the <a href="http://web2py.readthedocs.org/en/latest/" target="_blank">Sphinx docs</a>, watch <a href="http://vimeo.com/album/178500" target="_blank">videos</a>, and join a <a href="{{=URL('default', 'usergroups')}}">user group</a> for discussion. Take advantage of the <a href="http://www.web2py.com/layouts" target="_blank">layouts</a>, <a href="http://www.web2pyslices.com/home?content_type=Package" target="_blank">plugins</a>, <a href="http://www.web2py.com/appliances" target="_blank">appliances</a>, and <a href="http://web2pyslices.com" target="_blank">recipes</a>.</p>
</div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
</div>
</div>
<div class="row-fluid">
<div class="span12">
{{for k,quote in enumerate(quotes[:3]):}}
<div class="span4">
<p style="text-align: left"><em>{{=quote[0]}}</em></p>
<span class="right">
<a href="{{=quote[2]}}">{{=quote[1]}}</a>
</span>
</div>
{{pass}}
</div>
</div>
@@ -17,14 +17,11 @@
<ul>
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li>
<li><a target="_blank" href="http://www.planethost.com">PlanetHost</a> (USA)</li>
<li><a target="_blank" href="http://www.10biosystems.com">10BioSystems</a> (USA)</li>
<li><a target="_blank" href="http://www.formatics.nl">Formatics</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.corebyte.nl">Corebyte</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.onemewebservices.com">OneMeWebServices</a> (Canada)</li>
<li><a target="_blank" href="http://www.budgetbytes.nl">BudgetBytes</a> (The Netherlands)</li>
<li><a target="_blank" href="http://www.androsoft.pl">ANDROSoft</a> (Poland)</li>
<li><a target="_blank" href="http://www.sonnetech.com.br">Sonne Tech</a> (Brazil)</li>
<li><a target="_blank" href="http://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
<li><a target="_blank" href="http://itjp.net.br/">ITJP</a> (Brazil)</li>
@@ -32,15 +29,15 @@
<li><a target="_blank" href="http://www.definescope.com/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://lpfx.com.br">LPFX</a> (Brazil)</li>
<li><a target="_blank" href="http://emotionull.com">Emotionull</a> (Greece and Cyprus)</li>
<li><a target="_blank" href="http://www.vsa-services.com/">VSA Services</a> (Singapore)</li>
<li><a target="_blank" href="http://www.albendas.com">Albendas</a> (Spain)</li>
<li><a target="_blank" href="www.corebyte.nl">Corebyte</a> (Netherland)</li>
<li><a target="_blank" href="https://loadinfo-net.appspot.com">LoadInfo</a> (Bulgaria)</li>
<li><a target="_blank" href="http://www.appliedobjects.com">Applied Objects</a> (New Zealand)</li>
<li><a target="_blank" href="http://www.sistemasagiles.com.ar/">Sistemas Ágiles</a> ("Agile Systems") (Argentina)</li>
<li><a target="_blank" href="http://www.definescope.com/en/services/consulting/">DefineScope</a> (Portugal)</li>
<li><a target="_blank" href="http://10Biosystems.com">10BioSystems</a></li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</li>
<li><a target="_blank" href="http://www.tasko.it/">Tasko</a> (Italy)</li>
<li><a target="_blank" href="http://www.geekondemand.it/"> GeekOnDemand</a> (Italy)</li>
<li><a target="_blank" href="http://stifix.com"> Stifix</a> (Indonesia)</li>
<li><a target="_blank" href="http://www.garciac.es"> Garciac</a> (Spain)</li>
<li><a target="_blank" href="http://memoriapersistente.pt "> Memoria persistente</a> (Portugal)</li>
</ul>
</div>
@@ -5,6 +5,7 @@
{{block right_sidebar}}
<center>
<!--
<h3 class="feature-title">SITES POWERED BY WEB2PY</h3>
<a href="http://web2py.com/poweredby"><img class="frame" id="img1" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img2" width="200px"/></a>
@@ -14,7 +15,7 @@
<a href="http://web2py.com/poweredby"><img class="frame" id="img6" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img7" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a>
</div>
-->
</center>
<script>
function showimages() {
+66 -167
View File
@@ -1,173 +1,72 @@
<!--[if HTML5]><![endif]-->
<!DOCTYPE html>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ -->
<!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<!--[if IE 7]><html class="ie ie7 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<!--[if IE 8]><html class="ie ie8 ie-lte9 ie-lte8 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]-->
<head>
<title>{{=response.title or request.application}}</title>
<!--[if !HTML5]>
<meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}">
<![endif]-->
<!-- www.phpied.com/conditional-comments-block-downloads/ -->
<!-- Always force latest IE rendering engine
(even in intranet) & Chrome Frame
Remove this if you use the .htaccess -->
<meta charset="utf-8" />
<!-- http://dev.w3.org/html5/markup/meta.name.html -->
<meta name="application-name" content="{{=request.application}}" />
<!-- Speaking of Google, don't forget to set your site up:
http://google.com/webmasters -->
<meta name="google-site-verification" content="" />
<!-- Mobile Viewport Fix
j.mp/mobileviewport & davidbcalhoun.com/2010/viewport-metatag
device-width: Occupy full width of the screen in its current orientation
initial-scale = 1.0 retains dimensions instead of zooming out if page height > device height
user-scalable = yes allows the user to zoom in -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon">
<link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
<!-- All JavaScript at the bottom, except for Modernizr which enables
HTML5 elements & feature detects -->
<script src="{{=URL('static','js/modernizr.custom.js')}}"></script>
<!-- include stylesheets -->
{{
response.files.append(URL('static','css/web2py.css'))
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_bootstrap.css'))
response.files.append(URL('static','css/examples.css'))
}}
{{include 'web2py_ajax.html'}}
{{
# using sidebars need to know what sidebar you want to use
left_sidebar_enabled = globals().get('left_sidebar_enabled',False)
right_sidebar_enabled = globals().get('right_sidebar_enabled',False)
middle_columns = {0:'span12',1:'span9',2:'span6'}[
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<style>
th, td {color: black!important}
tbody tr:hover {background-color:transparent}
tbody tr {border-bottom: none}
p {text-align: left}
p, li { line-height: 1.6em}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
</style>
{{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}}
<!-- uncomment here to load jquery-ui
<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/web2py_bootstrap_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript>
{{block head}}{{end}}
</head>
<body>
<!-- Navbar ================================================== -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="flash">{{=response.flash or ''}}</div>
<div class="navbar-inner">
<div class="container">
<!-- the next tag is necessary for bootstrap menus, do not remove -->
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{{=response.logo or ''}}
<ul id="navbar" class="nav pull-right">{{='auth' in globals() and auth.navbar(mode="dropdown") or ''}}</ul>
<div class="nav-collapse">
{{is_mobile=request.user_agent().is_mobile}}
{{if response.menu:}}
{{=MENU(response.menu, _class='mobile-menu nav' if is_mobile else 'nav',mobile=is_mobile,li_class='dropdown',ul_class='dropdown-menu')}}
{{pass}}
</div><!--/.nav-collapse -->
</div>
</div>
</div><!--/top navbar -->
<div class="container">
<!-- Masthead ================================================== -->
<header class="mastheader" id="header">
<div class="span4">
<div class="page-header">
<img src="{{=URL('static','images/web2py_logo.png')}}" class="logo" alt="web2py logo" />
}}
{{include "web2py_ajax.html"}}
</head>
<body class="black">
<header class="black padded">
<div class="container middle max900">
<div class="fill middle">
<label class="ham padded fa fa-bars" for="menu"></label>
<div class="burger accordion">
<input type="checkbox" id="menu"/>
{{=MENU(response.menu,_class='menu')}}
</div>
</div>
</div>
</header>
</div>
<div class="container">
<section id="main" class="main row">
{{if left_sidebar_enabled:}}
<div class="span3 left-sidebar">
{{block left_sidebar}}
<h3>Left Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
<div class="{{=middle_columns}}">
{{block center}}
{{include}}
{{end}}
</div>
{{if right_sidebar_enabled:}}
<div class="span3">
{{block right_sidebar}}
<h3>Right Sidebar</h3>
<p></p>
{{end}}
</div>
{{pass}}
</section><!--/main-->
<!-- Footer ================================================== -->
<div class="row">
<footer class="footer span12" id="footer">
<div class="footer-content">
{{block footer}} <!-- this is default footer -->
<div id="poweredBy" class="pull-right">
{{=T('Copyright')}} &#169; {{=request.now.year}} -
{{=T('Powered by')}}
<a href="http://www.web2py.com/">web2py</a> -
{{=T('Hosted by')}}
<a href="http://pythonanywhere.com">PythonAnywhere</a>
</div>
{{end}}
</div>
</footer>
{{if response.flash:}}
<div class="w2p_flash">
{{=response.flash}}
</div>
</div> <!-- /container -->
<!-- The javascript =============================================
(Placed at the end of the document so the pages load faster) -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script>
<!--[if lt IE 7 ]>
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script>
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script>
<![endif]-->
{{if response.google_analytics_id:}}
<script src="{{=URL('static','js/analytics.min.js')}}"></script>
<script type="text/javascript">
analytics.initialize({
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'}
});</script>
{{pass}}
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script>
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
<img src="{{=URL('static','images/questions.png')}}" />
</a>
</body>
{{pass}}
<main class="white">
<div class="container max900">
{{if left_sidebar_enabled:}}
<div class="quarter padded">{{block left_sidebar}}{{end}}</div>
{{pass}}
<div class="{{=middle_column}} padded">{{include}}</div>
{{if right_sidebar_enabled:}}
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
{{pass}}
</div>
<div class="silver center padded">
<a class="fa fa-twitter" href="https://twitter.com/web2py/"></a>
<a class="fa fa-facebook" href="https://www.facebook.com/web2py/"></a>
</div>
</main>
<footer class="black">
<div class="container padded max900">
<div class="fill">
Copyright @ 2016 - Powered by Web2py
</div>
</div>
</footer>
</body>
<script>
// prevent android horizontal scrolling
window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false);
</script>
</html>
@@ -1,3 +0,0 @@
{{extend 'layout.html'}}
{{=toolbar}}
+491 -480
View File
@@ -1,480 +1,491 @@
# coding: utf8
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(requires internet access, experimental)',
'(something like "it-it")': '(například "cs-cs")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01Searching: **%s** %%{file}': 'Hledání: **%s** %%{soubor}',
'About': 'O programu',
'About application': 'O aplikaci',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'Admin design page': 'Admin design page',
'Admin language': 'jazyk rozhraní',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'An error occured, please %s the page',
'and rename it:': 'a přejmenovat na:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Application',
'application "%s" uninstalled': 'application "%s" odinstalována',
'application compiled': 'aplikace zkompilována',
'Application name:': 'Název aplikace:',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'arguments',
'at char %s': 'at char %s',
'at line %s': 'at line %s',
'ATTENTION:': 'ATTENTION:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Back to wizard',
'Basics': 'Basics',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Buy this book': 'Koupit web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'Change password': 'Změna hesla',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'code',
'Code listing': 'Code listing',
'collapse/expand all': 'vše sbalit/rozbalit',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'continue': 'continue',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
'Copyright': 'Copyright',
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created On': 'Vytvořeno - kdy',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
'Current session': 'Aktuální relace',
'currently running': 'právě běží',
'currently saved or': 'uloženo nebo',
'customize me!': 'upravte mě!',
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Database administration',
'database administration': 'správa databáze',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'defines tables',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'delete plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
'Demo': 'Demo',
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'design': 'návrh',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'Disable': 'Zablokovat',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'docs': 'dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Nevíte kudy kam?',
'done!': 'hotovo!',
'Download': 'Stáhnout',
'download layouts': 'stáhnout moduly rozvržení stránky',
'download plugins': 'stáhnout zásuvné moduly',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'edit all',
'Edit application': 'Správa aplikace',
'edit controller': 'edit controller',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Exception %(extype)s: %(exvalue)s',
'Exception %s': 'Exception %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expand Abbreviation',
'export as csv file': 'exportovat do .csv souboru',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'file "%(filename)s" created',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'Filename': 'Název souboru',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Frames',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Goto',
'graph model': 'graph model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj světe',
'Help': 'Nápověda',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak jste se sem vlastně dostal?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'import': 'import',
'Import/Export': 'Import/Export',
'includes': 'zahrnuje',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'inspect attributes',
'Install': 'Instalovat',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid email': 'Neplatný email',
'Invalid password': 'Nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Is Active': 'Je aktivní',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings for ZenCoding Plugin',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjme',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online pokec',
'loading...': 'nahrávám...',
'locals': 'locals',
'Locals##debug': 'Lokální proměnné',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Manage',
'Manage Cache': 'Manage Cache',
'Menu Model': 'Model rozbalovací nabídky',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'New Application Wizard': 'Nový průvodce aplikací',
'New application wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit primitivní aplikaci',
'next': 'next',
'next 100 rows': 'dalších 100 řádků',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
'online designer': 'online návrhář',
'Online examples': 'Příklady online',
'Open new app in new window': 'Open new app in new window',
'or alternatively': 'or alternatively',
'Or Get from URL:': 'Or Get from URL:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
'Other Plugins': 'Ostatní moduly',
'Other Recipes': 'Ostatní zásuvné moduly',
'Overview': 'Přehled',
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack plugin',
'password': 'heslo',
'Password': 'Heslo',
"Password fields don't match": 'Hesla se neshodu',
'Peeking at file': 'Peeking at file',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" in application',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Plural Form #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Poháněno',
'Preface': 'Předmluva',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Python': 'Python',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
'record does not exist': 'záznam neexistuje',
'Record ID': 'ID záznamu',
'Record id': 'id záznamu',
'refresh': 'obnovte',
'register': 'registrovat',
'Register': 'Zaregistrovat se',
'Registration identifier': 'Registrační identifikátor',
'Registration key': 'Registrační klíč',
'reload': 'reload',
'Reload routes': 'Znovu nahrát cesty',
'Remember me (for 30 days)': 'Zapamatovat na 30 dní',
'Remove compiled': 'Odstranit zkompilované',
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'revert': 'vrátit se k původnímu',
'Role': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Save file:',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'session expired',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Singular Form': 'Singular Form',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'skip to generate',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Static',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Step',
'step': 'step',
'stop': 'stop',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Testing application',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Výstup ze souboru je slovník, který se zobrazil v pohledu %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'There are no controllers',
'There are no modules': 'There are no modules',
'There are no plugins': 'Žádné moduly nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'There are no static files',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'There are no views',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'This is the %(filename)s template',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Ticket',
'Ticket ID': 'Ticket ID',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Traceback',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'try something like',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'try view',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Unable to check for upgrades',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgrade now',
'upgrade now to %s': 'upgrade now to %s',
'upload': 'nahrát',
'Upload': 'Upload',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil',
'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval',
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'variables',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Web Framework',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'web2py upgrade',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve web2py',
'Welcome to web2py!': 'Vítejte ve web2py!',
'Which called the function %s located in the file %s': 'která zavolala funkci %s v souboru (kontroléru) %s.',
'You are successfully running web2py': 'Úspěšně jste spustili web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can modify this application and adapt it to your needs': 'Tuto aplikaci si můžete upravit a přizpůsobit ji svým potřebám.',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You visited the url %s': 'Navštívili jste stránku %s,',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
}
# -*- coding: utf-8 -*-
{
'!langcode!': 'cs-cz',
'!langname!': 'čeština',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': 'Kolonka "Upravit" je nepovinný výraz, například "pole1=\'nováhodnota\'". Výsledky databázového JOINu nemůžete mazat ani upravovat.',
'"User Exception" debug mode. An error ticket could be issued!': '"User Exception" debug mode. An error ticket could be issued!',
'%%{Row} in Table': '%%{řádek} v tabulce',
'%%{Row} selected': 'označených %%{řádek}',
'%s %%{row} deleted': '%s smazaných %%{záznam}',
'%s %%{row} updated': '%s upravených %%{záznam}',
'%s selected': '%s označených',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(vyžaduje připojení k internetu)',
'(requires internet access, experimental)': '(vyžaduje internetové připojení, experimentální)',
'(something like "it-it")': '(například "cs-cz")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '@markmin\x01(soubor **gluon/contrib/plural_rules/%s.py** nenalezen)',
'@markmin\x01An error occured, please [[reload %s]] the page': '@markmin\x01Došlo k chybě, prosím [[obnovte stránku %s]]',
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Hledání: **%s** %%{soubor}',
'About': 'O programu',
'About application': 'O aplikaci',
'Access Control': 'Řízení přístupu',
'Add breakpoint': 'Přidat bod přerušení',
'Additional code for your application': 'Další kód pro Vaši aplikaci',
'admin': 'admin',
'Admin design page': 'Admin design page',
'Admin language': 'jazyk rozhraní',
'Administrative interface': 'pro administrátorské rozhraní klikněte sem',
'Administrative Interface': 'Administrátorské rozhraní',
'administrative interface': 'rozhraní pro správu',
'Administrator Password:': 'Administrátorské heslo:',
'Ajax Recipes': 'Recepty s ajaxem',
'An error occured, please %s the page': 'Došlo k chybě, prosím %s stránku',
'and rename it:': 'a přejmenovat na:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin je zakázaná bez zabezpečeného spojení',
'Application': 'Aplikace',
'application "%s" uninstalled': 'application "%s" odinstalována',
'application compiled': 'aplikace zkompilována',
'Application name:': 'Název aplikace:',
'are not used': 'nepoužita',
'are not used yet': 'ještě nepoužita',
'Are you sure you want to delete this object?': 'Opravdu chcete odstranit tento objekt?',
'Are you sure you want to uninstall application "%s"?': 'Opravdu chcete odinstalovat aplikaci "%s"?',
'arguments': 'argumenty',
'at char %s': 'na pozici znaku %s',
'at line %s': 'na řádku %s',
'ATTENTION:': 'POZOR:',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'Available Databases and Tables': 'Dostupné databáze a tabulky',
'back': 'zpět',
'Back to wizard': 'Zpátky do průvodce',
'Basics': 'Základy',
'Begin': 'Začít',
'breakpoint': 'bod přerušení',
'Breakpoints': 'Body přerušení',
'breakpoints': 'body přerušení',
'Buy this book': 'Koupit Web2py knihu',
"Buy web2py's book": 'Koupit Web2py knihu',
'Cache': 'Cache',
'cache': 'cache',
'Cache Keys': 'Klíče cache',
'cache, errors and sessions cleaned': 'cache, chyby a relace byly pročištěny',
'can be a git repo': 'může to být git repo',
'Cancel': 'Storno',
'Cannot be empty': 'Nemůže být prázdné',
'Change Admin Password': 'Změnit heslo pro správu',
'Change admin password': 'Změnit heslo pro správu aplikací',
'Change password': 'Změna hesla',
'check all': 'vše označit',
'Check for upgrades': 'Zkusit aktualizovat',
'Check to delete': 'Označit ke smazání',
'Check to delete:': 'Označit ke smazání:',
'Checking for upgrades...': 'Zjišťuji, zda jsou k dispozici aktualizace...',
'Clean': 'Pročistit',
'Clear CACHE?': 'Vymazat CACHE?',
'Clear DISK': 'Vymazat DISK',
'Clear RAM': 'Vymazat RAM',
'Click row to expand traceback': 'Pro rozbalení stopy, klikněte na řádek',
'Click row to view a ticket': 'Pro zobrazení chyby (ticketu), klikněte na řádku...',
'Client IP': 'IP adresa klienta',
'code': 'kód',
'Code listing': 'Výpis kódu',
'collapse/expand all': 'vše sbalit/rozbalit',
'Community': 'Komunita',
'Compile': 'Zkompilovat',
'compiled application removed': 'zkompilovaná aplikace smazána',
'Components and Plugins': 'Komponenty a zásuvné moduly',
'Condition': 'Podmínka',
'Config.ini': 'Config.ini',
'continue': 'pokračovat',
'Controller': 'Kontrolér (Controller)',
'Controllers': 'Kontroléry',
'controllers': 'kontroléry',
'Copyright': 'Copyright',
'Count': 'Počet',
'Create': 'Vytvořit',
'create file with filename:': 'vytvořit soubor s názvem:',
'created by': 'vytvořil',
'Created By': 'Vytvořeno - kým',
'Created On': 'Vytvořeno - kdy',
'crontab': 'crontab',
'Current request': 'Aktuální požadavek',
'Current response': 'Aktuální odpověď',
'Current session': 'Aktuální relace',
'currently running': 'právě běží',
'currently saved or': 'uloženo nebo',
'customize me!': 'upravte mě!',
'data uploaded': 'data nahrána',
'Database': 'Rozhraní databáze',
'Database %s select': 'databáze %s výběr',
'Database administration': 'Administrace databáze',
'database administration': 'správa databáze',
'Date and Time': 'Datum a čas',
'day': 'den',
'db': 'db',
'DB Model': 'Databázový model',
'Debug': 'Ladění',
'defines tables': 'definuje tabulky',
'Delete': 'Smazat',
'delete': 'smazat',
'delete all checked': 'smazat vše označené',
'delete plugin': 'zrušit plugin',
'Delete this file (you will be asked to confirm deletion)': 'Smazat tento soubor (budete požádán o potvrzení mazání)',
'Delete:': 'Smazat:',
'deleted after first hit': 'smazat po prvním dosažení',
'Demo': 'Demo',
'Deploy': 'Nahrát',
'Deploy on Google App Engine': 'Nahrát na Google App Engine',
'Deploy to OpenShift': 'Nahrát na OpenShift',
'Deployment Recipes': 'Postupy pro deployment',
'Description': 'Popis',
'design': 'návrh',
'Design': 'Design',
'Detailed traceback description': 'Podrobný výpis prostředí',
'details': 'podrobnosti',
'direction: ltr': 'směr: ltr',
'Disable': 'Zablokovat',
'DISK': 'DISK',
'Disk Cache Keys': 'Klíče diskové cache',
'Disk Cleared': 'Disk smazán',
'docs': 'dokumentace',
'Documentation': 'Dokumentace',
"Don't know what to do?": 'Kde najdu další informace ?',
'done!': 'hotovo!',
'Download': 'Stáhnout',
'download layouts': 'stáhnout moduly rozvržení stránky',
'download plugins': 'stáhnout zásuvné moduly',
'E-mail': 'E-mail',
'Edit': 'Upravit',
'edit all': 'editovat vše',
'Edit application': 'Správa aplikace',
'edit controller': 'editovat controller',
'Edit current record': 'Upravit aktuální záznam',
'Edit Profile': 'Upravit profil',
'edit views:': 'upravit pohled:',
'Editing file "%s"': 'Úprava souboru "%s"',
'Editing Language file': 'Úprava jazykového souboru',
'Editing Plural Forms File': 'Editování souboru množných čísel',
'Email and SMS': 'Email a SMS',
'Enable': 'Odblokovat',
'enter a number between %(min)g and %(max)g': 'zadejte číslo mezi %(min)g a %(max)g',
'Enter an integer between %(min)g and %(max)g': 'Enter an integer between %(min)g and %(max)g',
'enter an integer between %(min)g and %(max)g': 'zadejte celé číslo mezi %(min)g a %(max)g',
'Error': 'Chyba',
'Error logs for "%(app)s"': 'Seznam výskytu chyb pro aplikaci "%(app)s"',
'Error snapshot': 'Snapshot chyby',
'Error ticket': 'Ticket chyby',
'Errors': 'Chyby',
'Exception %(extype)s: %(exvalue)s': 'Výjimka %(extype)s: %(exvalue)s',
'Exception %s': 'Výjimka %s',
'Exception instance attributes': 'Prvky instance výjimky',
'Expand Abbreviation': 'Expandovat zkratku',
'export as csv file': 'exportovat do .csv souboru',
'exposes': 'vystavuje',
'exposes:': 'vystavuje funkce:',
'extends': 'rozšiřuje',
'failed to compile file because:': 'soubor se nepodařilo zkompilovat, protože:',
'FAQ': 'Často kladené dotazy',
'File': 'Soubor',
'file': 'soubor',
'file "%(filename)s" created': 'soubor "%(filename)s" byl vytvořen',
'file saved on %(time)s': 'soubor uložen %(time)s',
'file saved on %s': 'soubor uložen %s',
'Filename': 'Název souboru',
'filter': 'filtr',
'Find Next': 'Najít další',
'Find Previous': 'Najít předchozí',
'First name': 'Křestní jméno',
'Forgot username?': 'Zapomněl jste svoje přihlašovací jméno?',
'forgot username?': 'zapomněl jste svoje přihlašovací jméno?',
'Forms and Validators': 'Formuláře a validátory',
'Frames': 'Framy',
'Free Applications': 'Aplikace zdarma',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Vytvořit',
'Get from URL:': 'Stáhnout z internetu:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Globals##debug': 'Globální proměnné',
'go!': 'OK!',
'Goto': 'Přejít na',
'graph model': 'grafický model',
'Group %(group_id)s created': 'Skupina %(group_id)s vytvořena',
'Group ID': 'ID skupiny',
'Groups': 'Skupiny',
'Hello World': 'Ahoj všichni',
'Help': 'Nápověda',
'Helping web2py': 'Podpořte Web2py',
'Hide/Show Translated strings': 'Skrýt/Zobrazit přeložené texty',
'Hits': 'Kolikrát dosaženo',
'Home': 'Domovská stránka',
'honored only if the expression evaluates to true': 'brát v potaz jen když se tato podmínka vyhodnotí kladně',
'How did you get here?': 'Jak se Ti tato stránka vlastně zobrazila?',
'If start the upgrade, be patient, it may take a while to download': 'If start the upgrade, be patient, it may take a while to download',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\r\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'import': 'import',
'Import/Export': 'Import/Export',
'includes': 'zahrnuje',
'Index': 'Index',
'insert new': 'vložit nový záznam ',
'insert new %s': 'vložit nový záznam %s',
'inspect attributes': 'prohlédnout atributy',
'Install': 'Instalovat',
'Installed applications': 'Nainstalované aplikace',
'Interaction at %s line %s': 'Interakce v %s, na řádce %s',
'Interactive console': 'Interaktivní příkazová řádka',
'Internal State': 'Vnitřní stav',
'Introduction': 'Úvod',
'Invalid email': 'Neplatný email',
'Invalid password': 'Nesprávné heslo',
'invalid password.': 'neplatné heslo',
'Invalid Query': 'Neplatný dotaz',
'invalid request': 'Neplatný požadavek',
'Is Active': 'Je aktiv',
'It is %s %%{day} today.': 'Dnes je to %s %%{den}.',
'Key': 'Klíč',
'Key bindings': 'Vazby klíčů',
'Key bindings for ZenCoding Plugin': 'Key bindings pro ZenCoding Plugin',
'languages': 'jazyky',
'Languages': 'Jazyky',
'Last name': 'Příjmení',
'Last saved on:': 'Naposledy uloženo:',
'Layout': 'Rozvržení stránky (layout)',
'Layout Plugins': 'Moduly rozvržení stránky (Layout Plugins)',
'Layouts': 'Rozvržení stránek',
'License for': 'Licence pro',
'Line number': 'Číslo řádku',
'LineNo': 'Č.řádku',
'Live Chat': 'Online chat',
'loading...': 'nahrávám...',
'locals': 'locals',
'Locals##debug': 'Lokální proměnné',
'Log In': 'Přihlásit se',
'Logged in': 'Přihlášení proběhlo úspěšně',
'Logged out': 'Odhlášení proběhlo úspěšně',
'Login': 'Přihlásit se',
'login': 'přihlásit se',
'Login to the Administrative Interface': 'Přihlásit se do Správce aplikací',
'logout': 'odhlásit se',
'Logout': 'Odhlásit se',
'Lost Password': 'Zapomněl jste heslo',
'Lost password?': 'Zapomněl jste heslo?',
'lost password?': 'zapomněl jste heslo?',
'Manage': 'Spravovat',
'Manage Cache': 'Spravovat cache',
'Menu Model': 'Model rozbalovací nabídky',
'Models': 'Modely',
'models': 'modely',
'Modified By': 'Změněno - kým',
'Modified On': 'Změněno - kdy',
'Modules': 'Moduly',
'modules': 'moduly',
'My Sites': 'Správa aplikací',
'Name': 'Jméno',
'new application "%s" created': 'nová aplikace "%s" vytvořena',
'New application wizard': 'Nový průvodce aplikací',
'New Application Wizard': 'Nový průvodce aplikací',
'New password': 'Nové heslo',
'New Record': 'Nový záznam',
'new record inserted': 'nový záznam byl založen',
'New simple application': 'Vytvořit novou aplikaci',
'next': 'další',
'next 100 rows': 'dalších 100 řádků',
'No databases in this application': 'V této aplikaci nejsou žádné databáze',
'No Interaction yet': 'Ještě žádná interakce nenastala',
'No ticket_storage.txt found under /private folder': 'Soubor ticket_storage.txt v adresáři /private nenalezen',
'Object or table name': 'Objekt či tabulka',
'Old password': 'Původní heslo',
'Online book': 'Online kniha',
'online designer': 'online návrhář',
'Online examples': 'Ukázka aplikace: web2py stránky',
'Open new app in new window': 'Otevřít novou aplikaci v novém okně',
'or alternatively': 'nebo případně',
'Or Get from URL:': 'Nebo získat z URL adresy:',
'or import from csv file': 'nebo importovat z .csv souboru',
'Origin': 'Původ',
'Original/Translation': 'Originál/Překlad',
'Other Plugins': 'Ostatní moduly',
'Other Recipes': 'Ostatní zásuvné moduly',
'Overview': 'Přehled',
'Overwrite installed app': 'Přepsat instalovanou aplikaci',
'Pack all': 'Zabalit',
'Pack compiled': 'Zabalit zkompilované',
'pack plugin': 'pack (zabalit) plugin',
'password': 'heslo',
'Password': 'Heslo',
"Password fields don't match": 'Hesla se neshodují',
'Peeking at file': 'Sledování souboru',
'Please': 'Prosím',
'Plugin "%s" in application': 'Plugin "%s" v aplikaci',
'plugins': 'zásuvné moduly',
'Plugins': 'Zásuvné moduly',
'Plural Form #%s': 'Množné číslo #%s',
'Plural-Forms:': 'Množná čísla:',
'Powered by': 'Používá technologii',
'Preface': 'Předmluva',
'previous 100 rows': 'předchozích 100 řádků',
'Private files': 'Soukromé soubory',
'private files': 'soukromé soubory',
'profile': 'profil',
'Project Progress': 'Vývoj projektu',
'Python': 'Python',
'Query:': 'Dotaz:',
'Quick Examples': 'Krátké příklady',
'RAM': 'RAM',
'RAM Cache Keys': 'Klíče RAM Cache',
'Ram Cleared': 'RAM smazána',
'Readme': 'Nápověda',
'Recipes': 'Postupy jak na to',
'Record': 'Záznam',
'record does not exist': 'záznam neexistuje',
'Record ID': 'ID záznamu',
'Record id': 'id záznamu',
'refresh': 'obnovte',
'register': 'registrovat',
'Register': 'Zaregistrovat se',
'Registration identifier': 'Registrační identifikátor',
'Registration key': 'Registrační klíč',
'reload': 'reload',
'Reload routes': 'Znovu nahrát cesty',
'Remember me (for 30 days)': 'Zapamatovat na 30 dní',
'Remove compiled': 'Odstranit zkompilované',
'Removed Breakpoint on %s at line %s': 'Bod přerušení smazán - soubor %s na řádce %s',
'Replace': 'Zaměnit',
'Replace All': 'Zaměnit vše',
'request': 'request',
'Reset Password key': 'Reset registračního klíče',
'response': 'response',
'restart': 'restart',
'restore': 'obnovit',
'Retrieve username': 'Získat přihlašovací jméno',
'return': 'return',
'revert': 'vrátit se k původnímu',
'Role': 'Role',
'Rows in Table': 'Záznamy v tabulce',
'Rows selected': 'Záznamů zobrazeno',
'rules are not defined': 'pravidla nejsou definována',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Spustí testy v tomto souboru (ke spuštění všech testů, použijte tlačítko 'test')",
'Running on %s': 'Běží na %s',
'Save': 'Uložit',
'Save file:': 'Uložit soubor:',
'Save via Ajax': 'Uložit pomocí Ajaxu',
'Saved file hash:': 'hash uloženého souboru:',
'Semantic': 'Modul semantic',
'Services': 'Služby',
'session': 'session',
'session expired': 'vypršela session',
'Set Breakpoint on %s at line %s: %s': 'Bod přerušení nastaven v souboru %s na řádce %s: %s',
'shell': 'příkazová řádka',
'Sign Up': 'Registrovat se',
'Singular Form': 'Jednotné číslo',
'Site': 'Správa aplikací',
'Size of cache:': 'Velikost cache:',
'skip to generate': 'přeskočit pro vytvoření',
'Sorry, could not find mercurial installed': 'Bohužel mercurial není nainstalován.',
'Start a new app': 'Vytvořit novou aplikaci',
'Start searching': 'Začít hledání',
'Start wizard': 'Spustit průvodce',
'state': 'stav',
'Static': 'Statické soubory',
'static': 'statické soubory',
'Static files': 'Statické soubory',
'Statistics': 'Statistika',
'Step': 'Krok',
'step': 'krok',
'stop': 'zastavit',
'Stylesheet': 'CSS styly',
'submit': 'odeslat',
'Submit': 'Odeslat',
'successful': 'úspěšně',
'Support': 'Podpora',
'Sure you want to delete this object?': 'Opravdu chcete smazat tento objekt?',
'Table': 'tabulka',
'Table name': 'Název tabulky',
'Temporary': 'Dočasný',
'test': 'test',
'Testing application': 'Zkušební aplikace',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"Dotaz" je podmínka, například "db.tabulka1.pole1==\'hodnota\'". Podmínka "db.tabulka1.pole1==db.tabulka2.pole2" pak vytvoří SQL JOIN.',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikace: každá URL je mapována na funkci vystavovanou kontrolérem.',
'The Core': 'Jádro (The Core)',
'The data representation, define database tables and sets': 'Reprezentace dat: definovat tabulky databáze a záznamy',
'The output of the file is a dictionary that was rendered by the view %s': 'Funkce vrátila dictionary (slovník) hodnot, a ty se vypsaly pomocí šablony %s.',
'The presentations layer, views are also known as templates': 'Prezentační vrstva: pohledy či templaty (šablony)',
'The Views': 'Pohledy (The Views)',
'There are no controllers': 'Nejsou vytvořeny žádné controllery',
'There are no modules': 'Nejsou přidány žádné moduly',
'There are no plugins': 'Žádné pluginy nejsou instalovány.',
'There are no private files': 'Žádné soukromé soubory neexistují.',
'There are no static files': 'Nejsou přidány žádné statické soubory',
'There are no translators, only default language is supported': 'There are no translators, only default language is supported',
'There are no views': 'Nejsou vytvořeny žádné šablony (views)',
'These files are not served, they are only available from within your app': 'Tyto soubory jsou klientům nepřístupné. K dispozici jsou pouze v rámci aplikace.',
'These files are served without processing, your images go here': 'Tyto soubory jsou servírovány bez přídavné logiky, sem patří např. obrázky.',
'This App': 'Tato aplikace',
'This is a copy of the scaffolding application': 'Toto je kopie aplikace skelet.',
'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk': 'This is an experimental feature and it needs more testing. If you decide to upgrade you do it at your own risk',
'This is the %(filename)s template': 'Toto je šablona %(filename)s',
'this page to see if a breakpoint was hit and debug interaction is required.': 'tuto stránku, abyste uviděli, zda se dosáhlo bodu přerušení.',
'Ticket': 'Tiket',
'Ticket ID': 'ID tiketu',
'Time in Cache (h:m:s)': 'Čas v Cache (h:m:s)',
'Timestamp': 'Časové razítko',
'to previous version.': 'k předchozí verzi.',
'To create a plugin, name a file/folder plugin_[name]': 'Zásuvný modul vytvoříte tak, že pojmenujete soubor/adresář plugin_[jméno modulu]',
'To emulate a breakpoint programatically, write:': 'K nastavení bodu přerušení v kódu programu, napište:',
'to use the debugger!': ', abyste mohli ladící program používat!',
'toggle breakpoint': 'vyp./zap. bod přerušení',
'Toggle Fullscreen': 'Na celou obrazovku a zpět',
'too short': 'Příliš krátké',
'Traceback': 'Hierarchie volání',
'Translation strings for the application': 'Překlad textů pro aplikaci',
'try something like': 'zkuste něco jako',
'Try the mobile interface': 'Zkuste rozhraní pro mobilní zařízení',
'try view': 'vyzkoušet šablonu (view)',
'Twitter': 'Twitter',
'Type python statement in here and hit Return (Enter) to execute it.': 'Type python statement in here and hit Return (Enter) to execute it.',
'Type some Python code in here and hit Return (Enter) to execute it.': 'Type some Python code in here and hit Return (Enter) to execute it.',
'Unable to check for upgrades': 'Nelze zjistit informaci o aktualizacích',
'unable to parse csv file': 'csv soubor nedá sa zpracovat',
'uncheck all': 'vše odznačit',
'Uninstall': 'Odinstalovat',
'update': 'aktualizovat',
'update all languages': 'aktualizovat všechny jazyky',
'Update:': 'Upravit:',
'Upgrade': 'Upgrade',
'upgrade now': 'upgradovat nyní',
'upgrade now to %s': 'upgradovat nyní na %s',
'upload': 'nahrát',
'Upload': 'Upload (nahrát)',
'Upload a package:': 'Nahrát balík:',
'Upload and install packed application': 'Nahrát a instalovat zabalenou aplikaci',
'upload file:': 'nahrát soubor:',
'upload plugin file:': 'nahrát soubor modulu:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Použijte (...)&(...) pro AND, (...)|(...) pro OR a ~(...) pro NOT pro sestavení složitějších dotazů.',
'User %(id)s Logged-in': 'Uživatel %(id)s přihlášen',
'User %(id)s Logged-out': 'Uživatel %(id)s odhlášen',
'User %(id)s Password changed': 'Uživatel %(id)s změnil heslo',
'User %(id)s Profile updated': 'Uživatel %(id)s upravil profil',
'User %(id)s Registered': 'Uživatel %(id)s se zaregistroval',
'User %(id)s Username retrieved': 'Uživatel %(id)s si nachal zaslat přihlašovací jméno',
'User ID': 'ID uživatele',
'Username': 'Přihlašovací jméno',
'variables': 'proměnné',
'Verify Password': 'Zopakujte heslo',
'Version': 'Verze',
'Version %s.%s.%s (%s) %s': 'Verze %s.%s.%s (%s) %s',
'Versioning': 'Verzování',
'Videos': 'Videa',
'View': 'Pohled (View)',
'Views': 'Pohledy',
'views': 'pohledy',
'Web Framework': 'Webový framework',
'web2py is up to date': 'Máte aktuální verzi web2py.',
'web2py online debugger': 'Ladící online web2py program',
'web2py Recent Tweets': 'Štěbetání na Twitteru o web2py',
'web2py upgrade': 'aktualizace Web2py',
'web2py upgraded; please restart it': 'Web2py bylo aktualizováno; prosím restarujte jej',
'Welcome': 'Vítejte',
'Welcome to web2py': 'Vitejte ve Web2py aplikaci',
'Welcome to web2py!': 'Vítejte ve Web2py aplikaci.',
'Which called the function %s located in the file %s': 'Tím byla zavolána funkce %s ze souboru (kontroléru) %s.',
'Working...': 'Pracuji...',
'You are successfully running web2py': 'Spustil(a) jsi webový server a Web2py.',
'You can also set and remove breakpoint in the edit window, using the Toggle Breakpoint button': 'Nastavovat a mazat body přerušení je též možno v rámci editování zdrojového souboru přes tlačítko Vyp./Zap. bod přerušení',
'You can inspect variables using the console bellow': 'Níže pomocí příkazové řádky si můžete prohlédnout proměnné',
'You can modify this application and adapt it to your needs': 'V ADMIN rozhraní můžeš Vytvořit novou aplikaci jako kopii ukázkové Welcome aplikace. A začít upravovat: modely, kontroléry, šablony pro URL adresy, které požaduješ.',
'You need to set up and reach a': 'Je třeba nejprve nastavit a dojít až na',
'You visited the url %s': 'Zadal jsi URL adresu %s.',
'Your application will be blocked until you click an action button (next, step, continue, etc.)': 'Aplikace bude blokována než se klikne na jedno z tlačítek (další, krok, pokračovat, atd.)',
}
+16 -8
View File
@@ -5,6 +5,9 @@
## File is released under public domain and you can use without limitations
#########################################################################
if request.global_settings.web2py_version < "2.14.1":
raise HTTP(500, "Requires web2py 2.13.3 or newer")
## if SSL/HTTPS is properly configured and you want all HTTP requests to
## be redirected to HTTPS, uncomment the line below:
# request.requires_https()
@@ -14,10 +17,12 @@ from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed
myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB
db = DAL(myconf.take('db.uri'), pool_size=myconf.take('db.pool_size', cast=int), check_reserved=['all'])
db = DAL(myconf.get('db.uri'),
pool_size = myconf.get('db.pool_size'),
migrate_enabled = myconf.get('db.migrate'),
check_reserved = ['all'])
else:
## connect to Google BigTable (optional 'google:datastore://namespace')
db = DAL('google:datastore+ndb')
@@ -32,8 +37,8 @@ else:
## none otherwise. a pattern can be 'controller/function.extension'
response.generic_patterns = ['*'] if request.is_local else []
## choose a style for forms
response.formstyle = myconf.take('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.take('forms.separator')
response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.get('forms.separator') or ''
## (optional) optimize handling of static files
@@ -53,7 +58,8 @@ response.form_label_separator = myconf.take('forms.separator')
from gluon.tools import Auth, Service, PluginManager
auth = Auth(db)
# host names must be a list of allowed host names (glob syntax allowed)
auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
plugins = PluginManager()
@@ -62,9 +68,11 @@ auth.define_tables(username=False, signature=False)
## configure email
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server')
mail.settings.sender = myconf.take('smtp.sender')
mail.settings.login = myconf.take('smtp.login')
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender')
mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False
## configure auth policy
auth.settings.registration_requires_verification = False
+4 -4
View File
@@ -12,10 +12,10 @@ response.title = request.application.replace('_',' ').title()
response.subtitle = ''
## read more at http://dev.w3.org/html5/markup/meta.name.html
response.meta.author = 'Your Name <you@example.com>'
response.meta.description = 'a cool new app'
response.meta.keywords = 'web2py, python, framework'
response.meta.generator = 'Web2py Web Framework'
response.meta.author = myconf.get('app.author')
response.meta.description = myconf.get('app.description')
response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id
response.google_analytics_id = None
+14 -3
View File
@@ -1,17 +1,28 @@
; App configuration
[app]
name = Welcome
author = Your Name <you@example.com>
description = a cool new app
keywords = web2py, python, framework
generator = Web2py Web Framework
; Host configuration
[host]
names = localhost:*, 127.0.0.1:*, *:*, *
; db configuration
[db]
uri = sqlite://storage.sqlite
migrate = 1
pool_size = 1
migrate = true
pool_size = 10 ; ignored for sqlite
; smtp address and credentials
[smtp]
server = smtp.gmail.com:587
sender = you@gmail.com
login = username:password
tls = true
ssl = true
; form styling
[forms]
File diff suppressed because one or more lines are too long
@@ -108,7 +108,7 @@ select.autocomplete {
background: url(../images/background.jpg) no-repeat center center;
}
body {
padding-top: 50px;
padding-top: 60px;
margin-bottom: 60px;
}
header {
@@ -233,7 +233,7 @@ div.error_wrapper {
line-height: 20px;
margin-right: 2px;
display: inline-block;
padding: 3px 5px;
padding: 6px 12px;
}
.web2py_counter {
margin-top: 5px;
@@ -270,6 +270,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console select,
.web2py_console a {
margin: 2px;
padding: 6px 12px;
}
#wiki_page_body {
width: 600px;
@@ -285,7 +286,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console .form-control {
width: 20%;
display: inline;
height: 100%;
height: 32px;
}
.web2py_console #w2p_keywords {
width: 50%;
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -120,7 +120,7 @@ args=()
class=handlers.RotatingFileHandler
level=DEBUG
formatter=simpleFormatter
args=("logs/web2py.log", "a", 1000000, 5)
args=("web2py.log", "a", 1000000, 5)
[handler_osxSysLogHandler]
class=handlers.SysLogHandler
Vendored
+15 -8
View File
@@ -5,6 +5,9 @@ import os
import datetime
import getpass
if os.path.exists('hosts'):
env.hosts = [h.strip() for h in open('hosts').readlines() if h.strip()]
env.hosts = env.hosts or raw_input('hostname (example.com):').split(',')
env.user = env.user or raw_input('username :')
@@ -85,7 +88,7 @@ def mkdir_or_backup(appname):
def git_deploy(appname, repo):
"""fab -H username@host git_deploy:appname,username/remoname"""
appfolder = applications+'/'+appname
backup = mkdir_or_backup(appfolder)
backup = mkdir_or_backup(appname)
if exists(appfolder):
with cd(appfolder):
@@ -95,7 +98,7 @@ def git_deploy(appname, repo):
with cd(applications):
sudo('git clone git@github.com/%s %s' % (repo, name))
sudo('chown -R www-data:www-data %s' % name)
def retrieve(appname=None):
"""fab -H username@host retrieve:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
@@ -115,18 +118,22 @@ def deploy(appname=None, all=False):
if os.path.exists('_update.zip'):
os.unlink('_update.zip')
backup = mkdir_or_backup(appfolder)
backup = mkdir_or_backup(appname)
if all=='all' or not backup:
local('zip -r _update.zip * -x *~ -x .* -x \#* -x *.bak -x *.bak2')
else:
local('zip -r _update.zip */*.py views/*.html views/*/*.html static/*')
put('_update.zip','/tmp/_update.zip')
with cd(appfolder):
sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
put('_update.zip','/tmp/_update.zip')
try:
with cd(appfolder):
sudo('unzip -o /tmp/_update.zip')
sudo('chown -R www-data:www-data *')
sudo('echo "%s" > DATE_DEPLOYMENT' % now)
finally:
sudo('rm /tmp/_update.zip')
if backup:
print 'TO RESTORE: fab restore:%s' % backup
+3 -3
View File
@@ -148,7 +148,7 @@ def app_compile(app, request, skip_failed_views=False):
failed_views = compile_application(folder, skip_failed_views)
return failed_views
except (Exception, RestrictedError):
tb = traceback.format_exc(sys.exc_info)
tb = traceback.format_exc()
remove_compiled_application(folder)
return tb
@@ -167,7 +167,7 @@ def app_create(app, request, force=False, key=None, info=False):
os.mkdir(path)
except:
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
elif not force:
@@ -197,7 +197,7 @@ def app_create(app, request, force=False, key=None, info=False):
except:
rmtree(path)
if info:
return False, traceback.format_exc(sys.exc_info)
return False, traceback.format_exc()
else:
return False
+2 -2
View File
@@ -676,8 +676,8 @@ def run_view_in(environment):
else:
filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views
x = view.replace('/', '_')
files = ['views_%s.pyc' % x]
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
is_compiled = os.path.exists(pjoin(path, files[0]))
# Don't use a generic view if the non-compiled view exists.
if is_compiled or (not is_compiled and not os.path.exists(filename)):
+21 -1
View File
@@ -35,7 +35,6 @@ from gluon.serializers import json_parser
locker = thread.allocate_lock()
def AppConfig(*args, **vars):
locker.acquire()
@@ -59,6 +58,27 @@ class AppConfigDict(dict):
dict.__init__(self, *args, **kwargs)
self.int_cache = {}
def get(self, path, default=None):
try:
value = self.take(path).strip()
if value.lower() in ('none','null',''):
return None
elif value.lower() == 'true':
return True
elif value.lower() == 'false':
return False
elif value.isdigit() or (value[0]=='-' and value[1:].isdigit()):
return int(value)
elif ',' in value:
return map(lambda x:x.strip(),value.split(','))
else:
try:
return float(value)
except:
return value
except:
return default
def take(self, path, cast=None):
parts = path.split('.')
if path in self.int_cache:
File diff suppressed because it is too large Load Diff
+11 -4
View File
@@ -27,10 +27,10 @@ from gluon import current
class RESIZE(object):
def __init__(self, nx=160, ny=80, quality=100,
def __init__(self, nx=160, ny=80, quality=100, padding = False,
error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message) = (
nx, ny, quality, error_message)
(self.nx, self.ny, self.quality, self.error_message, self.padding) = (
nx, ny, quality, error_message, padding)
def __call__(self, value):
if isinstance(value, str) and len(value) == 0:
@@ -41,7 +41,14 @@ class RESIZE(object):
img = Image.open(value.file)
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
s = cStringIO.StringIO()
img.save(s, 'JPEG', quality=self.quality)
if self.padding:
background = Image.new('RGBA', (self.nx, self.ny), (255, 255, 255, 0))
background.paste(
img,
((self.nx - img.size[0]) / 2, (self.ny - img.size[1]) / 2))
background.save(s, 'JPEG', quality=self.quality)
else:
img.save(s, 'JPEG', queality=self.quality)
s.seek(0)
value.file = s
except:
+24 -3
View File
@@ -36,11 +36,13 @@ def ldap_auth(server='ldap',
user_lastname_attrib='cn:2',
user_mail_attrib='mail',
manage_groups=False,
manage_groups_callback=[],
db=None,
group_dn=None,
group_name_attrib='cn',
group_member_attrib='memberUid',
group_filterstr='objectClass=*',
group_mapping={},
tls=False,
logging_level='error'):
@@ -207,6 +209,7 @@ def ldap_auth(server='ldap',
user_mail_attrib=user_mail_attrib,
manage_groups=manage_groups,
allowed_groups=allowed_groups,
group_mapping=group_mapping,
db=db):
if password == '': # http://tools.ietf.org/html/rfc4513#section-5.1.2
logger.warning('blank password not allowed')
@@ -262,6 +265,7 @@ def ldap_auth(server='ldap',
requested_attrs = ['sAMAccountName']
if manage_user:
requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
@@ -421,7 +425,8 @@ def ldap_auth(server='ldap',
store_user_mail = None
update_or_insert_values = {'first_name': store_user_firstname,
'last_name': store_user_lastname,
'email': store_user_mail}
'email': store_user_mail,
'username': username}
if '@' not in username:
# user as username
# ################
@@ -443,7 +448,7 @@ def ldap_auth(server='ldap',
con.unbind()
if manage_groups:
if not do_manage_groups(username, password):
if not do_manage_groups(username, password, group_mapping):
return False
return True
except ldap.INVALID_CREDENTIALS, e:
@@ -481,7 +486,7 @@ def ldap_auth(server='ldap',
# No match
return False
def do_manage_groups(username, password=None, db=db):
def do_manage_groups(username, password=None, group_mapping={}, db=db):
"""
Manage user groups
@@ -497,6 +502,14 @@ def ldap_auth(server='ldap',
ldap_groups_of_the_user = get_user_groups_from_ldap(
username, password)
if group_mapping != {}:
l = []
for group in ldap_groups_of_the_user:
if group in group_mapping:
l += group_mapping[group]
ldap_groups_of_the_user = l
logging.info("User groups after remapping: %s" % str(l))
#
# Get all group name where the user is in actually in local db
# #############################################################
@@ -539,6 +552,7 @@ def ldap_auth(server='ldap',
db_groups_of_the_user.append(group.role)
logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
auth_membership_changed = False
#
# Delete user membership from groups where user is not anymore
# #############################################################
@@ -546,6 +560,7 @@ def ldap_auth(server='ldap',
if ldap_groups_of_the_user.count(group_to_del) == 0:
db((db.auth_membership.user_id == db_user_id) &
(db.auth_membership.group_id == db_group_id[group_to_del])).delete()
auth_membership_changed = True
#
# Create user membership in groups where user is not in already
@@ -557,6 +572,12 @@ def ldap_auth(server='ldap',
else:
gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
db.auth_membership.insert(user_id=db_user_id, group_id=gid)
auth_membership_changed = True
if auth_membership_changed:
for callback in manage_groups_callback:
callback()
except:
logger.warning("[%s] Groups are not managed successfully!" % str(username))
import traceback
@@ -51,7 +51,7 @@ class OneallAccount(object):
reg_id=profile.get('identity_token','')
username=profile.get('preferredUsername',email)
first_name=name.get('givenName', dname.split(' ')[0])
last_name=profile.get('familyName',dname.split(' ')[1])
last_name=profile.get('familyName', dname.split(' ')[1] if(len(dname.split(' ')) > 1) else None)
return dict(registration_id=reg_id,username=username,email=email,
first_name=first_name,last_name=last_name)
self.mappings.default = defaultmapping
+90 -80
View File
@@ -2,42 +2,58 @@
Developed by niphlod@gmail.com
Released under web2py license because includes gluon/cache.py source code
"""
import redis
from redis.exceptions import ConnectionError
from gluon import current
from gluon.cache import CacheAbstract
try:
import cPickle as pickle
import cPickle as pickle
except:
import pickle
import pickle
import time
import re
import logging
import thread
import random
from gluon import current
from gluon.cache import CacheAbstract
from gluon.contrib.redis_utils import acquire_lock, release_lock
from gluon.contrib.redis_utils import register_release_lock, RConnectionError
logger = logging.getLogger("web2py.cache.redis")
locker = thread.allocate_lock()
def RedisCache(*args, **vars):
def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=False, db=None):
"""
Usage example: put in models
Usage example: put in models::
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True, password=None)
First of all install Redis
Ubuntu :
sudo apt-get install redis-server
sudo pip install redis
:param db: redis db to use (0..16)
:param debug: if True adds to stats() the total_hits and misses
:param with_lock: sets the default locking mode for creating new keys.
Then
from gluon.contrib.redis_utils import RConn
rconn = RConn()
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache(redis_conn=rconn, debug=True, with_lock=True)
Args:
redis_conn: a redis-like connection object
debug: if True adds to stats() the total_hits and misses
with_lock: sets the default locking mode for creating new keys.
By default is False (usualy when you choose Redis you do it
for performances reason)
When True, only one thread/process can set a value concurrently
fail_gracefully: if redis is unavailable, returns the value computing it
instead of raising an exception
It can be used pretty much the same as cache.ram()
When you use cache.redis directly you can use :
redis_key_and_var_name = cache.redis('redis_key_and_var_name', lambda or function,
time_expire=time.time(), with_lock=True)
When you use cache.redis directly you can use
value = cache.redis('mykey', lambda: time.time(), with_lock=True)
to enforce locking. The with_lock parameter overrides the one set in the
cache.redis instance creation
@@ -69,7 +85,9 @@ def RedisCache(*args, **vars):
try:
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name, RedisClient(*args, **vars))
setattr(RedisCache, instance_name,
RedisClient(redis_conn=redis_conn, debug=debug,
with_lock=with_lock, fail_gracefully=fail_gracefully))
return getattr(RedisCache, instance_name)
finally:
locker.release()
@@ -81,22 +99,19 @@ class RedisClient(object):
MAX_RETRIES = 5
RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False, password=None):
self.server = server
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
def __init__(self, redis_conn=None, debug=False,
with_lock=False, fail_gracefully=False):
self.request = current.request
self.debug = debug
self.with_lock = with_lock
self.prefix = "w2p:%s:" % (self.request.application)
self.fail_gracefully = fail_gracefully
self.prefix = "w2p:cache:%s:" % self.request.application
if self.request:
app = self.request.application
else:
app = ''
if not app in self.meta_storage:
if app not in self.meta_storage:
self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: {
'hit_total': 0,
@@ -105,9 +120,10 @@ class RedisClient(object):
else:
self.storage = self.meta_storage[app]
self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
self.cache_set_key = 'w2p:%s:___cache_set' % self.request.application
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
self.r_server = redis_conn
self._release_script = register_release_lock(self.r_server)
def initialize(self):
pass
@@ -121,90 +137,86 @@ class RedisClient(object):
value = None
ttl = 0
try:
#is there a value
# is there a value
obj = self.r_server.get(newKey)
#what's its ttl
# what's its ttl
if obj:
ttl = self.r_server.ttl(newKey)
if ttl > time_expire:
obj = None
if obj:
#was cached
# was cached
if self.debug:
self.r_server.incr('web2py_cache_statistics:hit_total')
value = pickle.loads(obj)
elif f is None:
#delete and never look back
# delete and never look back
self.r_server.delete(newKey)
else:
#naive distributed locking
# naive distributed locking
if with_lock:
lock_key = '%s:__lock' % newKey
try:
while True:
lock = self.r_server.setnx(lock_key, 1)
if lock:
value = self.cache_it(newKey, f, time_expire)
break
else:
time.sleep(0.2)
#did someone else create it in the meanwhile ?
obj = self.r_server.get(newKey)
if obj:
value = pickle.loads(obj)
break
finally:
self.r_server.delete(lock_key)
randomvalue = time.time()
al = acquire_lock(self.r_server, lock_key, randomvalue)
# someone may have computed it
obj = self.r_server.get(newKey)
if obj is None:
value = self.cache_it(newKey, f, time_expire)
else:
value = pickle.loads(obj)
release_lock(self, lock_key, al)
else:
#without distributed locking
# without distributed locking
value = self.cache_it(newKey, f, time_expire)
return value
except ConnectionError:
except RConnectionError:
return self.retry_call(key, f, time_expire, with_lock)
def cache_it(self, key, f, time_expire):
if self.debug:
self.r_server.incr('web2py_cache_statistics:misses')
cache_set_key = self.cache_set_key
expireat = int(time.time() + time_expire) + 120
bucket_key = "%s:%s" % (cache_set_key, expireat / 60)
expire_at = int(time.time() + time_expire) + 120
bucket_key = "%s:%s" % (cache_set_key, expire_at / 60)
value = f()
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
if time_expire == 0:
time_expire = 1
self.r_server.setex(key, value_, time_expire)
#print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat))
#print 'that will expire on %s' % (bucket_key, time.ctime(((expireat/60) + 1)*60))
self.r_server.setex(key, time_expire, value_)
# print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expire_at))
# print 'that will expire on %s' % (bucket_key, time.ctime(((expire_at / 60) + 1) * 60))
p = self.r_server.pipeline()
#add bucket to the fixed set
# add bucket to the fixed set
p.sadd(cache_set_key, bucket_key)
#sets the key
p.setex(key, value_, time_expire)
#add the key to the bucket
# sets the key
p.setex(key, time_expire, value_)
# add the key to the bucket
p.sadd(bucket_key, key)
#expire the bucket properly
p.expireat(bucket_key, ((expireat/60) + 1)*60)
# expire the bucket properly
p.expireat(bucket_key, ((expire_at / 60) + 1) * 60)
p.execute()
return value
def retry_call(self, key, f, time_expire, with_locking):
def retry_call(self, key, f, time_expire, with_lock):
self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping %s seconds before reconnecting" %
(2 * self.RETRIES))
logger.error("sleeping %s seconds before reconnecting" % (2 * self.RETRIES))
time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.__call__(key, f, time_expire, with_locking)
if self.fail_gracefully:
self.RETRIES = 0
return f()
return self.__call__(key, f, time_expire, with_lock)
else:
self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
if self.fail_gracefully:
return f
raise RConnectionError('Redis instance is unavailable')
def increment(self, key, value=1):
try:
newKey = self.__keyFormat__(key)
return self.r_server.incr(newKey, value)
except ConnectionError:
except RConnectionError:
return self.retry_increment(key, value)
def retry_increment(self, key, value):
@@ -212,12 +224,10 @@ class RedisClient(object):
if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping some seconds before reconnecting")
time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.increment(key, value)
else:
self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
raise RConnectionError('Redis instance is unavailable')
def clear(self, regex):
"""
@@ -225,9 +235,9 @@ class RedisClient(object):
clear cache entries
"""
r = re.compile(regex)
#get all buckets
# get all buckets
buckets = self.r_server.smembers(self.cache_set_key)
#get all keys in buckets
# get all keys in buckets
if buckets:
keys = self.r_server.sunion(buckets)
else:
@@ -237,8 +247,8 @@ class RedisClient(object):
for a in keys:
if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a)
if random.randrange(0,100) < 10:
#do this just once in a while (10% chance)
if random.randrange(0, 100) < 10:
# do this just once in a while (10% chance)
self.clear_buckets(buckets)
pipe.execute()
@@ -254,19 +264,19 @@ class RedisClient(object):
return self.r_server.delete(newKey)
def stats(self):
statscollector = self.r_server.info()
stats_collector = self.r_server.info()
if self.debug:
statscollector['w2p_stats'] = dict(
stats_collector['w2p_stats'] = dict(
hit_total=self.r_server.get(
'web2py_cache_statistics:hit_total'),
misses=self.r_server.get('web2py_cache_statistics:misses')
)
statscollector['w2p_keys'] = dict()
stats_collector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)):
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a)
return statscollector
stats_collector['w2p_keys']["%s_expire_in_sec" % a] = self.r_server.ttl(a)
return stats_collector
def __keyFormat__(self, key):
return '%s%s' % (self.prefix, key.replace(' ', '_'))
+791
View File
@@ -0,0 +1,791 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Created by niphlod@gmail.com
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Scheduler with redis backend
---------------------------------
"""
USAGE = """
## Example
For any existing app
Create File: app/models/scheduler.py ======
from gluon.contrib.redis_utils import RConn
from gluon.contrib.redis_scheduler import RScheduler
def demo1(*args,**vars):
print 'you passed args=%s and vars=%s' % (args, vars)
return 'done!'
def demo2():
1/0
rconn = RConn()
mysched = RScheduler(db, dict(demo1=demo1,demo2=demo2), ...., redis_conn=rconn)
## run worker nodes with:
cd web2py
python web2py.py -K app
"""
import os
import time
import socket
import datetime
import logging
path = os.getcwd()
if 'WEB2PY_PATH' not in os.environ:
os.environ['WEB2PY_PATH'] = path
try:
from gluon.contrib.simplejson import loads, dumps
except:
from simplejson import loads, dumps
IDENTIFIER = "%s#%s" % (socket.gethostname(), os.getpid())
logger = logging.getLogger('web2py.rscheduler.%s' % IDENTIFIER)
from gluon.utils import web2py_uuid
from gluon.storage import Storage
from gluon.scheduler import *
from gluon.scheduler import _decode_dict
from gluon.contrib.redis_utils import RWatchError
POLLING = 'POLLING'
class RScheduler(Scheduler):
def __init__(self, db, tasks=None, migrate=True,
worker_name=None, group_names=None, heartbeat=HEARTBEAT,
max_empty_runs=0, discard_results=False, utc_time=False,
redis_conn=None, mode=1):
"""
Highly-experimental coordination with redis
Takes all args from Scheduler except redis_conn which
must be something closer to a StrictRedis instance.
My only regret - and the reason why I kept this under the hood for a
while - is that it's hard to hook up in web2py to something happening
right after the commit to a table, which will enable this version of the
scheduler to process "immediate" tasks right away instead of waiting a
few seconds (see FIXME in queue_task())
mode is reserved for future usage patterns.
Right now it moves the coordination (which is the most intensive
routine in the scheduler in matters of IPC) of workers to redis.
I'd like to have incrementally redis-backed modes of operations,
such as e.g.:
- 1: IPC through redis (which is the current implementation)
- 2: Store task results in redis (which will relieve further pressure
from the db leaving the scheduler_run table empty and possibly
keep things smooth as tasks results can be set to expire
after a bit of time)
- 3: Move all the logic for storing and queueing tasks to redis
itself - which means no scheduler_task usage too - and use
the database only as an historical record-bookkeeping
(e.g. for reporting)
As usual, I'm eager to see your comments.
"""
Scheduler.__init__(self, db, tasks=tasks, migrate=migrate,
worker_name=worker_name, group_names=group_names,
heartbeat=heartbeat, max_empty_runs=max_empty_runs,
discard_results=discard_results, utc_time=utc_time)
self.r_server = redis_conn
from gluon import current
self._application = current.request.application or 'appname'
def _nkey(self, key):
"""Helper to restrict all keys to a namespace
and track them"""
prefix = 'w2p:rsched:%s' % self._application
allkeys = '%s:allkeys' % prefix
newkey = "%s:%s" % (prefix, key)
self.r_server.sadd(allkeys, newkey)
return newkey
def prune_all(self):
"""
Just to be fair and implement a method
that does housekeeping
"""
all_keys = self._nkey('allkeys')
with self.r_server.pipeline() as pipe:
while True:
try:
pipe.watch('PRUNE_ALL')
while True:
k = pipe.spop(all_keys)
if k is None:
break
pipe.delete(k)
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
def dt2str(self, value):
return value.strftime('%Y-%m-%d %H:%M:%S')
def str2date(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
def send_heartbeat(self, counter):
"""
workers coordination has evolved into something is not that
easy. Here we try to do what we need in a single transaction,
and retry that transaction if something goes wrong
"""
with self.r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SEND_HEARTBEAT')
self.inner_send_heartbeat(counter, pipe)
pipe.execute()
self.adj_hibernation()
self.sleep()
break
except RWatchError:
time.sleep(0.1)
continue
def inner_send_heartbeat(self, counter, pipe):
"""
Does a few things:
- registers the workers
- accepts commands sent to workers (KILL, TERMINATE, PICK, DISABLED, etc)
- adjusts sleep
- saves stats
- elects master
- does "housecleaning" for dead workers
- triggers tasks assignment
"""
r_server = pipe
status_keyset = self._nkey('worker_statuses')
status_key = self._nkey('worker_status:%s' % (self.worker_name))
now = self.now()
mybackedstatus = r_server.hgetall(status_key)
if not mybackedstatus:
r_server.hmset(
status_key,
dict(
status=ACTIVE, worker_name=self.worker_name,
first_heartbeat=self.dt2str(now),
last_heartbeat=self.dt2str(now),
group_names=dumps(self.group_names), is_ticker=False,
worker_stats=dumps(self.w_stats))
)
r_server.sadd(status_keyset, status_key)
if not self.w_stats.status == POLLING:
self.w_stats.status = ACTIVE
self.w_stats.sleep = self.heartbeat
mybackedstatus = ACTIVE
else:
mybackedstatus = mybackedstatus['status']
if mybackedstatus == DISABLED:
# keep sleeping
self.w_stats.status = DISABLED
r_server.hmset(
status_key,
dict(last_heartbeat=self.dt2str(now),
worker_stats=dumps(self.w_stats))
)
elif mybackedstatus == TERMINATE:
self.w_stats.status = TERMINATE
logger.debug("Waiting to terminate the current task")
self.give_up()
elif mybackedstatus == KILL:
self.w_stats.status = KILL
self.die()
else:
if mybackedstatus == STOP_TASK:
logger.info('Asked to kill the current task')
self.terminate_process()
logger.info('........recording heartbeat (%s)',
self.w_stats.status)
r_server.hmset(
status_key,
dict(
last_heartbeat=self.dt2str(now), status=ACTIVE,
worker_stats=dumps(self.w_stats)
)
)
# newroutine
r_server.expire(status_key, self.heartbeat * 3 * 15)
self.w_stats.sleep = self.heartbeat # re-activating the process
if self.w_stats.status not in (RUNNING, POLLING):
self.w_stats.status = ACTIVE
self.do_assign_tasks = False
if counter % 5 == 0 or mybackedstatus == PICK:
try:
logger.info(
' freeing workers that have not sent heartbeat')
registered_workers = r_server.smembers(status_keyset)
allkeys = self._nkey('allkeys')
for worker in registered_workers:
w = r_server.hgetall(worker)
w = Storage(w)
if not w:
r_server.srem(status_keyset, worker)
logger.info('removing %s from %s', worker, allkeys)
r_server.srem(allkeys, worker)
continue
try:
self.is_a_ticker = self.being_a_ticker(pipe)
except:
pass
if self.w_stats.status in (ACTIVE, POLLING):
self.do_assign_tasks = True
if self.is_a_ticker and self.do_assign_tasks:
# I'm a ticker, and 5 loops passed without reassigning tasks,
# let's do that and loop again
if not self.db_thread:
logger.debug('thread building own DAL object')
self.db_thread = DAL(
self.db._uri, folder=self.db._adapter.folder)
self.define_tables(self.db_thread, migrate=False)
db = self.db_thread
self.wrapped_assign_tasks(db)
return None
except:
logger.error('Error assigning tasks')
def being_a_ticker(self, pipe):
"""
This is slightly more convoluted than the original
but if far more efficient
"""
r_server = pipe
status_keyset = self._nkey('worker_statuses')
registered_workers = r_server.smembers(status_keyset)
ticker = None
all_active = []
all_workers = []
for worker in registered_workers:
w = r_server.hgetall(worker)
if w['worker_name'] != self.worker_name and w['status'] == ACTIVE:
all_active.append(w)
if w['is_ticker'] == 'True' and ticker is None:
ticker = w
all_workers.append(w)
not_busy = self.w_stats.status in (ACTIVE, POLLING)
if not ticker:
if not_busy:
# only if this worker isn't busy, otherwise wait for a free one
for worker in all_workers:
key = self._nkey('worker_status:%s' % worker['worker_name'])
if worker['worker_name'] == self.worker_name:
r_server.hset(key, 'is_ticker', True)
else:
r_server.hset(key, 'is_ticker', False)
logger.info("TICKER: I'm a ticker")
else:
# giving up, only if I'm not alone
if len(all_active) > 1:
key = self._nkey('worker_status:%s' % (self.worker_name))
r_server.hset(key, 'is_ticker', False)
else:
not_busy = True
return not_busy
else:
logger.info(
"%s is a ticker, I'm a poor worker" % ticker['worker_name'])
return False
def assign_tasks(self, db):
"""
The real beauty. We don't need to ASSIGN tasks, we just put
them into the relevant queue
"""
st, sd = db.scheduler_task, db.scheduler_task_deps
r_server = self.r_server
now = self.now()
status_keyset = self._nkey('worker_statuses')
with r_server.pipeline() as pipe:
while 1:
try:
# making sure we're the only one doing the job
pipe.watch('ASSIGN_TASKS')
registered_workers = pipe.smembers(status_keyset)
all_workers = []
for worker in registered_workers:
w = pipe.hgetall(worker)
if w['status'] == ACTIVE:
all_workers.append(Storage(w))
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
# build workers as dict of groups
wkgroups = {}
for w in all_workers:
group_names = loads(w.group_names)
for gname in group_names:
if gname not in wkgroups:
wkgroups[gname] = dict(
workers=[{'name': w.worker_name, 'c': 0}])
else:
wkgroups[gname]['workers'].append(
{'name': w.worker_name, 'c': 0})
# set queued tasks that expired between "runs" (i.e., you turned off
# the scheduler): then it wasn't expired, but now it is
db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(st.stop_time < now)
).update(status=EXPIRED)
# calculate dependencies
deps_with_no_deps = db(
(sd.can_visit == False) &
(~sd.task_child.belongs(
db(sd.can_visit == False)._select(sd.task_parent)
)
)
)._select(sd.task_child)
no_deps = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
((st.times_run < st.repeats) | (st.repeats == 0)) &
(st.start_time <= now) &
((st.stop_time == None) | (st.stop_time > now)) &
(st.next_run_time <= now) &
(st.enabled == True) &
(st.id.belongs(no_deps))
)
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
# let's freeze it up
db.commit()
x = 0
r_server = self.r_server
for group in wkgroups.keys():
queued_list = self._nkey('queued:%s' % group)
queued_set = self._nkey('queued_set:%s' % group)
# if are running, let's don't assign them again
running_list = self._nkey('running:%s' % group)
while True:
# the joys for rpoplpush!
t = r_server.rpoplpush(running_list, queued_list)
if not t:
# no more
break
r_server.sadd(queued_set, t)
tasks = all_available(st.group_name == group).select(
limitby=(0, limit), orderby = st.next_run_time)
# put tasks in the processing list
for task in tasks:
x += 1
gname = task.group_name
if r_server.sismember(queued_set, task.id):
# already queued, we don't put on the list
continue
r_server.sadd(queued_set, task.id)
r_server.lpush(queued_list, task.id)
d = dict(status=QUEUED)
if not task.task_name:
d['task_name'] = task.function_name
db(
(st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED)))
).update(**d)
db.commit()
# I didn't report tasks but I'm working nonetheless!!!!
if x > 0:
self.w_stats.empty_runs = 0
self.w_stats.queue = x
self.w_stats.distribution = wkgroups
self.w_stats.workers = len(all_workers)
# I'll be greedy only if tasks queued are equal to the limit
# (meaning there could be others ready to be queued)
self.greedy = x >= limit
logger.info('TICKER: workers are %s', len(all_workers))
logger.info('TICKER: tasks are %s', x)
def pop_task(self, db):
r_server = self.r_server
st = self.db.scheduler_task
task = None
# ready to process something
for group in self.group_names:
queued_set = self._nkey('queued_set:%s' % group)
queued_list = self._nkey('queued:%s' % group)
running_list = self._nkey('running:%s' % group)
running_dict = self._nkey('running_dict:%s' % group)
self.w_stats.status = POLLING
# polling for 1 minute in total. If more groups are in,
# polling is 1 minute in total
logger.debug(' polling on %s', group)
task_id = r_server.brpoplpush(queued_list, running_list,
timeout=60 / len(self.group_names))
logger.debug(' finished polling')
self.w_stats.status = ACTIVE
if task_id:
r_server.hset(running_dict, task_id, self.worker_name)
r_server.srem(queued_set, task_id)
task = db(
(st.id == task_id) &
(st.status == QUEUED)
).select().first()
if not task:
r_server.lrem(running_list, 0, task_id)
r_server.hdel(running_dict, task_id)
r_server.lrem(queued_list, 0, task_id)
logger.error("we received a task that isn't there (%s)",
task_id)
return None
break
now = self.now()
if task:
task.update_record(status=RUNNING, last_run_time=now)
# noone will touch my task!
db.commit()
logger.debug(' work to do %s', task.id)
else:
logger.info('nothing to do')
return None
times_run = task.times_run + 1
if not task.prevent_drift:
next_run_time = task.last_run_time + datetime.timedelta(
seconds=task.period
)
else:
# calc next_run_time based on available slots
# see #1191
next_run_time = task.start_time
secondspassed = self.total_seconds(now - next_run_time)
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)
if times_run < task.repeats or task.repeats == 0:
# need to run (repeating task)
run_again = True
else:
# no need to run again
run_again = False
run_id = 0
while True and not self.discard_results:
logger.debug(' new scheduler_run record')
try:
run_id = db.scheduler_run.insert(
task_id=task.id,
status=RUNNING,
start_time=now,
worker_name=self.worker_name)
db.commit()
break
except:
time.sleep(0.5)
db.rollback()
logger.info('new task %(id)s "%(task_name)s"'
' %(application_name)s.%(function_name)s' % task)
return Task(
app=task.application_name,
function=task.function_name,
timeout=task.timeout,
args=task.args, # in json
vars=task.vars, # in json
task_id=task.id,
run_id=run_id,
run_again=run_again,
next_run_time=next_run_time,
times_run=times_run,
stop_time=task.stop_time,
retry_failed=task.retry_failed,
times_failed=task.times_failed,
sync_output=task.sync_output,
uuid=task.uuid,
group_name=task.group_name)
def report_task(self, task, task_report):
"""
Needs overwriting only because we need to pop from the
running tasks
"""
r_server = self.r_server
db = self.db
now = self.now()
st = db.scheduler_task
sr = db.scheduler_run
if not self.discard_results:
if task_report.result != 'null' or task_report.tb:
# result is 'null' as a string if task completed
# if it's stopped it's None as NoneType, so we record
# the STOPPED "run" anyway
logger.debug(' recording task report in db (%s)',
task_report.status)
db(sr.id == task.run_id).update(
status=task_report.status,
stop_time=now,
run_result=task_report.result,
run_output=task_report.output,
traceback=task_report.tb)
else:
logger.debug(' deleting task report in db because of no result')
db(sr.id == task.run_id).delete()
# if there is a stop_time and the following run would exceed it
is_expired = (task.stop_time
and task.next_run_time > task.stop_time
and True or False)
status = (task.run_again and is_expired and EXPIRED
or task.run_again and not is_expired
and QUEUED or COMPLETED)
if task_report.status == COMPLETED:
# assigned calculations
d = dict(status=status,
next_run_time=task.next_run_time,
times_run=task.times_run,
times_failed=0,
assigned_worker_name=self.worker_name
)
db(st.id == task.task_id).update(**d)
if status == COMPLETED:
self.update_dependencies(db, task.task_id)
else:
st_mapping = {'FAILED': 'FAILED',
'TIMEOUT': 'TIMEOUT',
'STOPPED': 'FAILED'}[task_report.status]
status = (task.retry_failed
and task.times_failed < task.retry_failed
and QUEUED or task.retry_failed == -1
and QUEUED or st_mapping)
db(st.id == task.task_id).update(
times_failed=st.times_failed + 1,
next_run_time=task.next_run_time,
status=status,
assigned_worker_name=self.worker_name
)
logger.info('task completed (%s)', task_report.status)
running_list = self._nkey('running:%s' % task.group_name)
running_dict = self._nkey('running_dict:%s' % task.group_name)
r_server.lrem(running_list, 0, task.task_id)
r_server.hdel(running_dict, task.task_id)
def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
db = self.db
db.commit() # another nifty db.commit() only for Mysql
x = 0
while x < 10:
try:
rtn = self.pop_task(db)
return rtn
break
# this is here to "interrupt" any blrpoplpush op easily
except KeyboardInterrupt:
self.give_up()
break
except:
self.w_stats.errors += 1
db.rollback()
logger.error(' error popping tasks')
x += 1
time.sleep(0.5)
def get_workers(self, only_ticker=False):
""" Returns a dict holding worker_name : {**columns}
representing all "registered" workers
only_ticker returns only the worker running as a TICKER,
if there is any
"""
r_server = self.r_server
status_keyset = self._nkey('worker_statuses')
registered_workers = r_server.smembers(status_keyset)
all_workers = {}
for worker in registered_workers:
w = r_server.hgetall(worker)
w = Storage(w)
if not w:
continue
all_workers[w.worker_name] = Storage(
status=w.status,
first_heartbeat=self.str2date(w.first_heartbeat),
last_heartbeat=self.str2date(w.last_heartbeat),
group_names=loads(w.group_names, object_hook=_decode_dict),
is_ticker=w.is_ticker == 'True' and True or False,
worker_stats=loads(w.worker_stats, object_hook=_decode_dict)
)
if only_ticker:
for k, v in all_workers.iteritems():
if v['is_ticker']:
return {k: v}
return {}
return all_workers
def set_worker_status(self, group_names=None, action=ACTIVE,
exclude=None, limit=None, worker_name=None):
"""Internal function to set worker's status"""
r_server = self.r_server
all_workers = self.get_workers()
if not group_names:
group_names = self.group_names
elif isinstance(group_names, str):
group_names = [group_names]
exclusion = exclude and exclude.append(action) or [action]
workers = []
if worker_name is not None:
if worker_name in all_workers.keys():
workers = [worker_name]
else:
for k, v in all_workers.iteritems():
if v.status not in exclusion and set(group_names) & set(v.group_names):
workers.append(k)
if limit and worker_name is None:
workers = workers[:limit]
if workers:
with r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SET_WORKER_STATUS')
for w in workers:
worker_key = self._nkey('worker_status:%s' % w)
pipe.hset(worker_key, 'status', action)
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
def queue_task(self, function, pargs=[], pvars={}, **kwargs):
"""
FIXME: immediate should put item in queue. The hard part is
that currently there are no hooks happening at post-commit time
Queue tasks. This takes care of handling the validation of all
parameters
Args:
function: the function (anything callable with a __name__)
pargs: "raw" args to be passed to the function. Automatically
jsonified.
pvars: "raw" kwargs to be passed to the function. Automatically
jsonified
kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they should
be jsonified already, and they will override pargs and pvars
Returns:
a dict just as a normal validate_and_insert(), plus a uuid key
holding the uuid of the queued task. If validation is not passed
( i.e. some parameters are invalid) both id and uuid will be None,
and you'll get an "error" dict holding the errors found.
"""
if hasattr(function, '__name__'):
function = function.__name__
targs = 'args' in kwargs and kwargs.pop('args') or dumps(pargs)
tvars = 'vars' in kwargs and kwargs.pop('vars') or dumps(pvars)
tuuid = 'uuid' in kwargs and kwargs.pop('uuid') or web2py_uuid()
tname = 'task_name' in kwargs and kwargs.pop('task_name') or function
immediate = 'immediate' in kwargs and kwargs.pop('immediate') or None
rtn = self.db.scheduler_task.validate_and_insert(
function_name=function,
task_name=tname,
args=targs,
vars=tvars,
uuid=tuuid,
**kwargs)
if not rtn.errors:
rtn.uuid = tuuid
if immediate:
r_server = self.r_server
ticker = self.get_workers(only_ticker=True)
if ticker.keys():
ticker = ticker.keys()[0]
with r_server.pipeline() as pipe:
while True:
try:
pipe.watch('SET_WORKER_STATUS')
worker_key = self._nkey('worker_status:%s' % ticker)
pipe.hset(worker_key, 'status', 'PICK')
pipe.execute()
break
except RWatchError:
time.sleep(0.1)
continue
else:
rtn.uuid = None
return rtn
def stop_task(self, ref):
"""Shortcut for task termination.
If the task is RUNNING it will terminate it, meaning that status
will be set as FAILED.
If the task is QUEUED, its stop_time will be set as to "now",
the enabled flag will be set to False, and the status to STOPPED
Args:
ref: can be
- an integer : lookup will be done by scheduler_task.id
- a string : lookup will be done by scheduler_task.uuid
Returns:
- 1 if task was stopped (meaning an update has been done)
- None if task was not found, or if task was not RUNNING or QUEUED
Note:
Experimental
"""
r_server = self.r_server
st = self.db.scheduler_task
if isinstance(ref, int):
q = st.id == ref
elif isinstance(ref, str):
q = st.uuid == ref
else:
raise SyntaxError(
"You can retrieve results only by id or uuid")
task = self.db(q).select(st.id, st.status, st.group_name)
task = task.first()
rtn = None
if not task:
return rtn
running_dict = self._nkey('running_dict:%s' % task.group_name)
if task.status == 'RUNNING':
worker_key = r_server.hget(running_dict, task.id)
worker_key = self._nkey('worker_status:%s' % (worker_key))
r_server.hset(worker_key, 'status', STOP_TASK)
elif task.status == 'QUEUED':
rtn = self.db(q).update(
stop_time=self.now(),
enabled=False,
status=STOPPED)
return rtn
+46 -75
View File
@@ -1,25 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Developed by niphlod@gmail.com
License MIT/BSD/GPL
Redis-backed sessions
"""
import redis
from gluon import current
from gluon.storage import Storage
import time
import logging
import thread
from gluon import current
from gluon.storage import Storage
from gluon.contrib.redis_utils import acquire_lock, release_lock
from gluon.contrib.redis_utils import register_release_lock
logger = logging.getLogger("web2py.session.redis")
locker = thread.allocate_lock()
def RedisSession(*args, **vars):
def RedisSession(redis_conn, session_expiry=False, with_lock=False, db=None):
"""
Usage example: put in models
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False, password=None)
session.connect(request, response, db = sessiondb)
Usage example: put in models::
from gluon.contrib.redis_utils import RConn
rconn = RConn()
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession(redis_conn=rconn, with_lock=True, session_expiry=False)
session.connect(request, response, db = sessiondb)
Args:
redis_conn: a redis-like connection object
with_lock: prevent concurrent modifications to the same session
session_expiry: delete automatically sessions after n seconds
(still need to run sessions2trash.py every 1M sessions
or so)
Simple slip-in storage for session
"""
@@ -28,7 +43,8 @@ def RedisSession(*args, **vars):
try:
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisSession, instance_name):
setattr(RedisSession, instance_name, RedisClient(*args, **vars))
setattr(RedisSession, instance_name,
RedisClient(redis_conn, session_expiry=session_expiry, with_lock=with_lock))
return getattr(RedisSession, instance_name)
finally:
locker.release()
@@ -36,30 +52,9 @@ def RedisSession(*args, **vars):
class RedisClient(object):
meta_storage = {}
MAX_RETRIES = 5
RETRIES = 0
_release_script = None
def __init__(self, server='localhost:6379', db=None, debug=False,
session_expiry=False, with_lock=False, password=None):
"""session_expiry can be an integer, in seconds, to set the default expiration
of sessions. The corresponding record will be deleted from the redis instance,
and there's virtually no need to run sessions2trash.py
"""
self.server = server
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
self.debug = debug
if current and current.request:
self.app = current.request.application
else:
self.app = ''
self.r_server = redis.Redis(host=host, port=port, db=self.db, password=self.password)
if with_lock:
RedisClient._release_script = self.r_server.register_script(_LUA_RELEASE_LOCK)
def __init__(self, redis_conn, session_expiry=False, with_lock=False):
self.r_server = redis_conn
self._release_script = register_release_lock(self.r_server)
self.tablename = None
self.session_expiry = session_expiry
self.with_lock = with_lock
@@ -93,12 +88,11 @@ class RedisClient(object):
class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
# here self.db is the RedisClient instance
self.db = db
self.r_server = r_server
self.tablename = tablename
# set the namespace for sessions of this app
self.keyprefix = 'w2p:sess:%s' % tablename.replace(
'web2py_session_', '')
self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
# fast auto-increment id (needed for session handling)
self.serial = "%s:serial" % self.keyprefix
# index of all the session keys of this app
@@ -126,7 +120,7 @@ class MockTable(object):
if key == 'id':
# return a fake query. We need to query it just by id for normal operations
self.query = MockQuery(
field='id', db=self.r_server,
field='id', db=self.db,
prefix=self.keyprefix, session_expiry=self.session_expiry,
with_lock=self.with_lock, unique_key=self.unique_key
)
@@ -140,12 +134,12 @@ class MockTable(object):
# 'locked', 'client_ip','created_datetime','modified_datetime'
# 'unique_key', 'session_data'
# retrieve a new key
newid = str(self.r_server.incr(self.serial))
newid = str(self.db.r_server.incr(self.serial))
key = self.keyprefix + ':' + newid
if self.with_lock:
key_lock = key + ':lock'
acquire_lock(self.r_server, key_lock, newid)
with self.r_server.pipeline() as pipe:
acquire_lock(self.db.r_server, key_lock, newid)
with self.db.r_server.pipeline() as pipe:
# add it to the index
pipe.sadd(self.id_idx, key)
# set a hash key with the Storage
@@ -154,7 +148,7 @@ class MockTable(object):
pipe.expire(key, self.session_expiry)
pipe.execute()
if self.with_lock:
release_lock(self.r_server, key_lock, newid)
release_lock(self.db, key_lock, newid)
return newid
@@ -186,8 +180,8 @@ class MockQuery(object):
# means that someone wants to retrieve the key self.value
key = self.keyprefix + ':' + str(self.value)
if self.with_lock:
acquire_lock(self.db, key + ':lock', self.value)
rtn = self.db.hgetall(key)
acquire_lock(self.db.r_server, key + ':lock', self.value, 2)
rtn = self.db.r_server.hgetall(key)
if rtn:
if self.unique_key:
# make sure the id and unique_key are correct
@@ -201,13 +195,13 @@ class MockQuery(object):
rtn = []
id_idx = "%s:id_idx" % self.keyprefix
# find all session keys of this app
allkeys = self.db.smembers(id_idx)
allkeys = self.db.r_server.smembers(id_idx)
for sess in allkeys:
val = self.db.hgetall(sess)
val = self.db.r_server.hgetall(sess)
if not val:
if self.session_expiry:
# clean up the idx, because the key expired
self.db.srem(id_idx, sess)
self.db.r_server.srem(id_idx, sess)
continue
val = Storage(val)
# add a delete_record method (necessary for sessions2trash.py)
@@ -222,9 +216,9 @@ class MockQuery(object):
# means that the session has been found and needs an update
if self.op == 'eq' and self.field == 'id' and self.value:
key = self.keyprefix + ':' + str(self.value)
if not self.db.exists(key):
if not self.db.r_server.exists(key):
return None
with self.db.pipeline() as pipe:
with self.db.r_server.pipeline() as pipe:
pipe.hmset(key, kwargs)
if self.session_expiry:
pipe.expire(key, self.session_expiry)
@@ -238,7 +232,7 @@ class MockQuery(object):
if self.op == 'eq' and self.field == 'id' and self.value:
id_idx = "%s:id_idx" % self.keyprefix
key = self.keyprefix + ':' + str(self.value)
with self.db.pipeline() as pipe:
with self.db.r_server.pipeline() as pipe:
pipe.delete(key)
pipe.srem(id_idx, key)
rtn = pipe.execute()
@@ -254,29 +248,6 @@ class RecordDeleter(object):
def __call__(self):
id_idx = "%s:id_idx" % self.keyprefix
# remove from the index
self.db.srem(id_idx, self.key)
self.db.r_server.srem(id_idx, self.key)
# remove the key itself
self.db.delete(self.key)
def acquire_lock(conn, lockname, identifier, ltime=10):
while True:
if conn.set(lockname, identifier, ex=ltime, nx=True):
return identifier
time.sleep(.01)
_LUA_RELEASE_LOCK = """
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
"""
def release_lock(conn, lockname, identifier):
return RedisClient._release_script(
keys=[lockname], args=[identifier],
client=conn)
self.db.r_server.delete(self.key)
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Developed by niphlod@gmail.com
License MIT/BSD/GPL
Serves as base to implement Redis connection object and various utils
for redis_cache, redis_session and redis_scheduler in the future
Should-could be overriden in case redis doesn't keep up (e.g. cluster support)
to ensure compatibility with another - similar - library
"""
import logging
import thread
import time
from gluon import current
logger = logging.getLogger("web2py.redis_utils")
try:
import redis
from redis.exceptions import WatchError as RWatchError
from redis.exceptions import ConnectionError as RConnectionError
except ImportError:
logger.error("Needs redis library to work")
raise RuntimeError('Needs redis library to work')
locker = thread.allocate_lock()
def RConn(*args, **vars):
"""
Istantiates a StrictRedis connection with parameters, at the first time
only
"""
locker.acquire()
try:
instance_name = 'redis_conn_' + current.request.application
if not hasattr(RConn, instance_name):
setattr(RConn, instance_name, redis.StrictRedis(*args, **vars))
return getattr(RConn, instance_name)
finally:
locker.release()
def acquire_lock(conn, lockname, identifier, ltime=10):
while True:
if conn.set(lockname, identifier, ex=ltime, nx=True):
return identifier
time.sleep(.01)
_LUA_RELEASE_LOCK = """
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
"""
def release_lock(instance, lockname, identifier):
return instance._release_script(
keys=[lockname], args=[identifier])
def register_release_lock(conn):
rtn = conn.register_script(_LUA_RELEASE_LOCK)
return rtn
+6 -5
View File
@@ -72,12 +72,13 @@ def _default_validators(db, field):
if not field.notnull:
requires = validators.IS_EMPTY_OR(requires)
return requires
# does not get here for reference and list:reference
if field.unique:
requires.append(validators.IS_NOT_IN_DB(db, field))
sff = ['in', 'do', 'da', 'ti', 'de', 'bo']
if field.notnull and not field_type[:2] in sff:
requires.append(validators.IS_NOT_EMPTY())
elif not field.notnull and field_type[:2] in sff and requires:
requires.insert(0,validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string','upload','text','password','boolean']
if (field.notnull or field.unique) and not field_type in excluded_fields:
requires.insert(0,validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires:
requires[0] = validators.IS_EMPTY_OR(requires[0])
return requires
+16 -11
View File
@@ -362,20 +362,25 @@ class Request(Storage):
redirect(URL(scheme='https', args=self.args, vars=self.vars))
def restful(self):
def wrapper(action, self=self):
def f(_action=action, _self=self, *a, **b):
self.is_restful = True
method = _self.env.request_method
if len(_self.args) and '.' in _self.args[-1]:
_self.args[-1], _, self.extension = self.args[-1].rpartition('.')
def wrapper(action, request=self):
def f(_action=action, *a, **b):
request.is_restful = True
env = request.env
is_json = env.content_type=='application/json'
method = env.request_method
if len(request.args) and '.' in request.args[-1]:
request.args[-1], _, request.extension = request.args[-1].rpartition('.')
current.response.headers['Content-Type'] = \
contenttype('.' + _self.extension.lower())
contenttype('.' + request.extension.lower())
rest_action = _action().get(method, None)
if not (rest_action and method == method.upper()
and callable(rest_action)):
raise HTTP(405, "method not allowed")
try:
return rest_action(*_self.args, **getattr(_self, 'vars', {}))
res = rest_action(*request.args, **request.vars)
if is_json and not isinstance(res, str):
res = json(res)
return res
except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback)) == 1:
@@ -807,7 +812,7 @@ class Session(Storage):
response.session_data_name = 'session_data_%s' % masterapp.lower()
response.session_cookie_expires = cookie_expires
response.session_client = str(request.client).replace(':', '.')
response.session_cookie_key = cookie_key
current._session_cookie_key = cookie_key
response.session_cookie_compression_level = compression_level
# check if there is a session_id in cookies
@@ -1060,7 +1065,7 @@ class Session(Storage):
# if not cookie_key, but session_data_name in cookies
# expire session_data_name from cookies
if not response.session_cookie_key:
if not current._session_cookie_key:
if response.session_data_name in cookies:
rcookies[response.session_data_name] = 'expired'
rcookies[response.session_data_name]['path'] = '/'
@@ -1123,7 +1128,7 @@ class Session(Storage):
name = response.session_data_name
compression_level = response.session_cookie_compression_level
value = secure_dumps(dict(self),
response.session_cookie_key,
current._session_cookie_key,
compression_level=compression_level)
rcookies = response.cookies
rcookies.pop(name, None)
+106 -95
View File
@@ -116,6 +116,7 @@ __all__ = [
DEFAULT_PASSWORD_DISPLAY = '*' * 8
def xmlescape(data, quote=True):
"""
Returns an escaped string of the provided data
@@ -139,12 +140,14 @@ def xmlescape(data, quote=True):
data = cgi.escape(data, quote).replace("'", "&#x27;")
return data
def call_as_list(f, *a, **b):
if not isinstance(f, (list, tuple)):
f = [f]
for item in f:
item(*a, **b)
def truncate_string(text, length, dots='...'):
text = text.decode('utf-8')
if len(text) > length:
@@ -152,27 +155,26 @@ def truncate_string(text, length, dots='...'):
return text
def URL(
a=None,
c=None,
f=None,
r=None,
args=None,
vars=None,
anchor='',
extension=None,
env=None,
hmac_key=None,
hash_vars=True,
salt=None,
user_signature=None,
scheme=None,
host=None,
port=None,
encode_embedded_slash=False,
url_encode=True,
language=None,
):
def URL(a=None,
c=None,
f=None,
r=None,
args=None,
vars=None,
anchor='',
extension=None,
env=None,
hmac_key=None,
hash_vars=True,
salt=None,
user_signature=None,
scheme=None,
host=None,
port=None,
encode_embedded_slash=False,
url_encode=True,
language=None
):
"""
generates a url '/a/c/f' corresponding to application a, controller c
and function f. If r=request is passed, a, c, f are set, respectively,
@@ -256,10 +258,6 @@ def URL(
>>> str(URL(a='a', c='c', f='f', anchor='%(id)d', url_encode=True))
'/a/c/f#%25%28id%29d'
"""
from rewrite import url_out # done here in case used not-in web2py
@@ -439,7 +437,7 @@ def verifyURL(request, hmac_key=None, hash_vars=True, salt=None, user_signature=
"""
if not '_signature' in request.get_vars:
if '_signature' not in request.get_vars:
return False # no signature in the request URL
# check if user_signature requires
@@ -539,19 +537,24 @@ class XmlComponent(object):
return CAT(*components)
def add_class(self, name):
""" add a class to _class attribute """
"""
add a class to _class attribute
"""
c = self['_class']
classes = (set(c.split()) if c else set()) | set(name.split())
self['_class'] = ' '.join(classes) if classes else None
return self
def remove_class(self, name):
""" remove a class from _class attribute """
"""
remove a class from _class attribute
"""
c = self['_class']
classes = (set(c.split()) if c else set()) - 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
@@ -660,11 +663,11 @@ class XML(XmlComponent):
"""
to be considered experimental since the behavior of this method
is questionable
another option could be `TAG(self.text).elements(*args,**kwargs)`
another option could be `TAG(self.text).elements(*args, **kwargs)`
"""
return []
### important to allow safe session.flash=T(....)
# ## important to allow safe session.flash=T(....)
def XML_unpickle(data):
@@ -757,7 +760,7 @@ class DIV(XmlComponent):
Examples:
>>> a=DIV()
>>> a.insert(0,SPAN('x'))
>>> a.insert(0, SPAN('x'))
>>> print a
<div><span>x</span></div>
"""
@@ -853,7 +856,7 @@ class DIV(XmlComponent):
"""
components = []
for c in self.components:
if isinstance(c, (allowed_parents,CAT)):
if isinstance(c, (allowed_parents, CAT)):
pass
elif wrap_lambda:
c = wrap_lambda(c)
@@ -1024,7 +1027,7 @@ class DIV(XmlComponent):
Examples:
>>> a = DIV(DIV(SPAN('x'),3,DIV(SPAN('y'))))
>>> for c in a.elements('span',first_only=True): c[0]='z'
>>> for c in a.elements('span', first_only=True): c[0]='z'
>>> print a
<div><div><span>z</span>3<div><span>y</span></div></div></div>
>>> for c in a.elements('span'): c[0]='z'
@@ -1056,7 +1059,7 @@ class DIV(XmlComponent):
>>> 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
>>> print a # We should .xml() here instead of print
<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
@@ -1172,7 +1175,7 @@ class DIV(XmlComponent):
# loop the components
if find_text or find_components:
i = 0
while i<len(self.components):
while i < len(self.components):
c = self[i]
j = i+1
if check and find_text and isinstance(c, str) and \
@@ -1265,6 +1268,7 @@ class __tag_div__(DIV):
copy_reg.pickle(__tag_div__, TAG_pickler, TAG_unpickler)
class __TAG__(XmlComponent):
"""
@@ -1589,6 +1593,7 @@ class A(DIV):
self['_data-w2p_pre_call'] = self['pre_call']
return DIV.xml(self)
class BUTTON(DIV):
tag = 'button'
@@ -1863,11 +1868,11 @@ class INPUT(DIV):
print traceback.format_exc()
msg = "Validation error, field:%s %s" % (name,validator)
raise Exception(msg)
if not errors is None:
if errors is not None:
self.vars[name] = value
self.errors[name] = errors
break
if not name in self.errors:
if name not in self.errors:
self.vars[name] = value
return True
return False
@@ -1882,7 +1887,7 @@ class INPUT(DIV):
_value = None
else:
_value = str(self['_value'])
if '_checked' in self.attributes and not 'value' in self.attributes:
if '_checked' in self.attributes and 'value' not in self.attributes:
pass
elif t == 'checkbox':
if not _value:
@@ -1912,8 +1917,7 @@ class INPUT(DIV):
if name and hasattr(self, 'errors') \
and self.errors.get(name, None) \
and self['hideerror'] != True:
self['_class'] = (self['_class'] and self['_class']
+ ' ' or '') + 'invalidinput'
self['_class'] = (self['_class'] and self['_class'] + ' ' or '') + 'invalidinput'
return DIV.xml(self) + DIV(
DIV(
self.errors[name], _class='error',
@@ -1941,11 +1945,11 @@ class TEXTAREA(INPUT):
tag = 'textarea'
def _postprocessing(self):
if not '_rows' in self.attributes:
if '_rows' not in self.attributes:
self['_rows'] = 10
if not '_cols' in self.attributes:
if '_cols' not in self.attributes:
self['_cols'] = 40
if not self['value'] is None:
if self['value'] is not None:
self.components = [self['value']]
elif self.components:
self['value'] = self.components[0]
@@ -1956,7 +1960,7 @@ class OPTION(DIV):
tag = 'option'
def _fixup(self):
if not '_value' in self.attributes:
if '_value' not in self.attributes:
self.attributes['_value'] = str(self.components[0])
@@ -2012,11 +2016,10 @@ class SELECT(INPUT):
options = itertools.chain(*component_list)
value = self['value']
if not value is None:
if value is not None:
if not self['_multiple']:
for c in options: # my patch
if ((value is not None) and
(str(c['_value']) == str(value))):
if ((value is not None) and (str(c['_value']) == str(value))):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2026,8 +2029,7 @@ class SELECT(INPUT):
else:
values = [str(value)]
for c in options: # my patch
if ((value is not None) and
(str(c['_value']) in values)):
if ((value is not None) and (str(c['_value']) in values)):
c['_selected'] = 'selected'
else:
c['_selected'] = None
@@ -2077,16 +2079,15 @@ class FORM(DIV):
def assert_status(self, status, request_vars):
return status
def accepts(
self,
request_vars,
session=None,
formname='default',
keepvalues=False,
onvalidation=None,
hideerror=False,
**kwargs
):
def accepts(self,
request_vars,
session=None,
formname='default',
keepvalues=False,
onvalidation=None,
hideerror=False,
**kwargs
):
"""
kwargs is not used but allows to specify the same interface for FORM and SQLFORM
"""
@@ -2128,8 +2129,7 @@ class FORM(DIV):
onsuccess = onvalidation.get('onsuccess', None)
onfailure = onvalidation.get('onfailure', None)
onchange = onvalidation.get('onchange', None)
if [k for k in onvalidation if not k in (
'onsuccess', 'onfailure', 'onchange')]:
if [k for k in onvalidation if k not in ('onsuccess', 'onfailure', 'onchange')]:
raise RuntimeError('Invalid key in onvalidate dict')
if onsuccess and status:
call_as_list(onsuccess, self)
@@ -2144,7 +2144,7 @@ class FORM(DIV):
call_as_list(onvalidation, self)
if self.errors:
status = False
if not session is None:
if session is not None:
if hasattr(self, 'record_hash'):
formkey = self.record_hash + ':' + web2py_uuid()
else:
@@ -2158,25 +2158,22 @@ class FORM(DIV):
return status
def _postprocessing(self):
if not '_action' in self.attributes:
if '_action' not in self.attributes:
self['_action'] = '#'
if not '_method' in self.attributes:
if '_method' not in self.attributes:
self['_method'] = 'post'
if not '_enctype' in self.attributes:
if '_enctype' not in self.attributes:
self['_enctype'] = 'multipart/form-data'
def hidden_fields(self):
c = []
attr = self.attributes.get('hidden', {})
if 'hidden' in self.attributes:
c = [INPUT(_type='hidden', _name=key, _value=value)
for (key, value) in attr.iteritems()]
c = [INPUT(_type='hidden', _name=key, _value=value) for (key, value) in attr.iteritems()]
if hasattr(self, 'formkey') and self.formkey:
c.append(INPUT(_type='hidden', _name='_formkey',
_value=self.formkey))
c.append(INPUT(_type='hidden', _name='_formkey', _value=self.formkey))
if hasattr(self, 'formname') and self.formname:
c.append(INPUT(_type='hidden', _name='_formname',
_value=self.formname))
c.append(INPUT(_type='hidden', _name='_formname', _value=self.formname))
return DIV(c, _style="display:none;")
def xml(self):
@@ -2221,8 +2218,7 @@ class FORM(DIV):
kwargs['request_vars'] = kwargs.get(
'request_vars', current.request.post_vars)
kwargs['session'] = kwargs.get('session', current.session)
kwargs['dbio'] = kwargs.get('dbio', False)
# necessary for SQLHTML forms
kwargs['dbio'] = kwargs.get('dbio', False) # necessary for SQLHTML forms
onsuccess = kwargs.get('onsuccess', 'flash')
onfailure = kwargs.get('onfailure', 'flash')
@@ -2301,8 +2297,7 @@ class FORM(DIV):
"""
kwargs['dbio'] = kwargs.get('dbio', True)
# necessary for SQLHTML forms
kwargs['dbio'] = kwargs.get('dbio', True) # necessary for SQLHTML forms
self.validate(**kwargs)
return self
@@ -2348,10 +2343,9 @@ class FORM(DIV):
def sanitizer(obj):
if isinstance(obj, dict):
for k in obj.keys():
if any([unsafe in str(k).upper() for
unsafe in UNSAFE]):
# erease unsafe pair
obj.pop(k)
if any([unsafe in str(k).upper() for unsafe in UNSAFE]):
# erease unsafe pair
obj.pop(k)
else:
# not implemented
pass
@@ -2377,8 +2371,10 @@ class FORM(DIV):
return [flatten(item) for item in newobj]
else:
return newobj
else: return str(newobj)
else: return newobj
else:
return str(newobj)
else:
return newobj
return flatten(d)
def as_json(self, sanitize=True):
@@ -2505,19 +2501,19 @@ class MENU(DIV):
self.data = data
self.attributes = args
self.components = []
if not '_class' in self.attributes:
if '_class' not in self.attributes:
self['_class'] = 'web2py-menu web2py-menu-vertical'
if not 'ul_class' in self.attributes:
if 'ul_class' not in self.attributes:
self['ul_class'] = 'web2py-menu-vertical'
if not 'li_class' in self.attributes:
if 'li_class' not in self.attributes:
self['li_class'] = 'web2py-menu-expand'
if not 'li_first' in self.attributes:
if 'li_first' not in self.attributes:
self['li_first'] = 'web2py-menu-first'
if not 'li_last' in self.attributes:
if 'li_last' not in self.attributes:
self['li_last'] = 'web2py-menu-last'
if not 'li_active' in self.attributes:
if 'li_active' not in self.attributes:
self['li_active'] = 'web2py-menu-active'
if not 'mobile' in self.attributes:
if 'mobile' not in self.attributes:
self['mobile'] = False
def serialize(self, data, level=0):
@@ -2587,12 +2583,11 @@ class MENU(DIV):
return self.serialize(self.data, 0).xml()
def embed64(
filename=None,
file=None,
data=None,
extension='image/gif',
):
def embed64(filename=None,
file=None,
data=None,
extension='image/gif'
):
"""
helper to encode the provided (binary) data into base64.
@@ -2610,6 +2605,7 @@ def embed64(
return 'data:%s;base64,%s' % (extension, data)
# TODO: Check if this test() is still relevant now that we have gluon/tests/test_html.py
def test():
"""
Example:
@@ -2833,11 +2829,26 @@ class MARKMIN(XmlComponent):
def __str__(self):
return self.xml()
def ASSIGNJS(**kargs):
"""
Example:
ASSIGNJS(var1='1', var2='2') will return the following javascript variables assignations :
var var1 = "1";
var var2 = "2";
Args:
**kargs: Any keywords arguments and assigned values.
Returns:
Javascript vars assignations for the key/value passed.
"""
from gluon.serializers import json
s = ""
for key, value in kargs.items():
s+='var %s = %s;\n' % (key, json(value))
s += 'var %s = %s;\n' % (key, json(value))
return XML(s)
+24
View File
@@ -61,7 +61,13 @@ PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))'
PY_M_STRING_LITERAL_RE = r'(?<=[^\w]T\.M\()(?P<name>'\
+ r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\
+ r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
+ r'(?:"(?:[^"\\]|\\.)*"))'
regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
regex_translate_m = re.compile(PY_M_STRING_LITERAL_RE, re.DOTALL)
regex_param = re.compile(r'{(?P<s>.+?)}')
# pattern for a valid accept_language
@@ -960,6 +966,7 @@ def findT(path, language=DEFAULT_LANGUAGE):
+ listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
data = read_locked(filename)
items = regex_translate.findall(data)
items += regex_translate_m.findall(data)
for item in items:
try:
message = safe_eval(item)
@@ -995,6 +1002,23 @@ def update_all_languages(application_path):
findT(application_path, language[:-3])
def update_from_langfile(target, source):
"""this will update untranslated messages in target from source (where both are language files)
this can be used as first step when creating language file for new but very similar language
or if you want update your app from welcome app of newer web2py version
or in non-standard scenarios when you work on target and from any reason you have partial translation in source
"""
src = read_dict(source)
sentences = read_dict(target)
for key in sentences:
val = sentences[key]
if not val or val == key:
new_val = src.get(key)
if new_val and new_val != val:
sentences[key] = new_val
write_dict(target, sentences)
if __name__ == '__main__':
import doctest
doctest.testmod()
+3 -4
View File
@@ -80,10 +80,9 @@ locale.setlocale(locale.LC_CTYPE, "C") # IMPORTANT, web2py requires locale "C"
exists = os.path.exists
pjoin = os.path.join
logpath = abspath("logging.conf")
if exists(logpath):
try:
logging.config.fileConfig(abspath("logging.conf"))
else:
except: # fails on GAE or when logfile is missing
logging.basicConfig()
logger = logging.getLogger("web2py")
@@ -361,7 +360,7 @@ def wsgibase(environ, responder):
local_hosts = global_settings.local_hosts
client = get_client(env)
x_req_with = str(env.http_x_requested_with).lower()
cmd_opts = request.global_settings.cmd_options
cmd_opts = global_settings.cmd_options
request.update(
client = client,
+1 -1
View File
@@ -53,8 +53,8 @@ except:
except:
try:
import win32con
import win32file
import pywintypes
import win32file
os_locking = 'windows'
except:
pass
+4 -18
View File
@@ -9,7 +9,7 @@
Generates names for cache and session files
--------------------------------------------
"""
import os, uuid
import os
def generate(filename, depth=2, base=512):
@@ -17,10 +17,10 @@ def generate(filename, depth=2, base=512):
path, filename = os.path.split(filename)
else:
path = None
dummyhash = sum(ord(c)*256**(i % 4) for i, c in enumerate(filename)) % base**depth
dummyhash = sum(ord(c) * 256 ** (i % 4) for i, c in enumerate(filename)) % base ** depth
folders = []
for level in range(depth-1, -1, -1):
code, dummyhash = divmod(dummyhash, base**level)
for level in range(depth - 1, -1, -1):
code, dummyhash = divmod(dummyhash, base ** level)
folders.append("%03x" % code)
folders.append(filename)
if path:
@@ -63,17 +63,3 @@ def open(filename, mode="r", path=None):
if mode.startswith('w') and not os.path.exists(os.path.dirname(fullfilename)):
os.makedirs(os.path.dirname(fullfilename))
return file(fullfilename, mode)
def test():
if not os.path.exists('tests'):
os.mkdir('tests')
for k in range(20):
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
open(filename, "w").write('test')
assert open(filename, "r").read() == 'test'
if exists(filename):
remove(filename)
if __name__ == '__main__':
test()
+4 -4
View File
@@ -642,7 +642,7 @@ def regex_url_in(request, environ):
items = filename.split('/', 1)
if regex_version.match(items[0]):
version, filename = items
static_folder = pjoin(request.env.applications_parent,
static_folder = pjoin(global_settings.applications_parent,
'applications', application, 'static')
static_file = os.path.abspath(pjoin(static_folder, filename))
if not static_file.startswith(static_folder):
@@ -947,7 +947,7 @@ class MapUrlIn(object):
if len(self.args) == 1 and self.arg0 in self.router.root_static:
self.controller = self.request.controller = 'static'
root_static_file = pjoin(self.request.env.applications_parent,
root_static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
self.controller, self.arg0)
log_rewrite("route: root static=%s" % root_static_file)
@@ -1016,11 +1016,11 @@ class MapUrlIn(object):
# if language-specific file doesn't exist, try same file in static
#
if self.language:
static_file = pjoin(self.request.env.applications_parent,
static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
'static', self.language, file)
if not self.language or not isfile(static_file):
static_file = pjoin(self.request.env.applications_parent,
static_file = pjoin(global_settings.applications_parent,
'applications', self.application,
'static', file)
self.extension = None
+43
View File
@@ -1870,3 +1870,46 @@ class WSGIWorker(Worker):
sock_file.close()
# Monolithic build...end of module: rocket/methods/wsgi.py
def demo_app(environ, start_response):
global static_folder
import os
types = {'htm': 'text/html','html': 'text/html','gif': 'image/gif',
'jpg': 'image/jpeg','png': 'image/png','pdf': 'applications/pdf'}
if static_folder:
if not static_folder.startswith('/'):
static_folder = os.path.join(os.getcwd(),static_folder)
path = os.path.join(static_folder, environ['PATH_INFO'][1:] or 'index.html')
type = types.get(path.split('.')[-1],'text')
if os.path.exists(path):
try:
data = open(path,'rb').read()
start_response('200 OK', [('Content-Type', type)])
except IOError:
start_response('404 NOT FOUND', [])
data = '404 NOT FOUND'
else:
start_response('500 INTERNAL SERVER ERROR', [])
data = '500 INTERNAL SERVER ERROR'
else:
start_response('200 OK', [('Content-Type', 'text/html')])
data = '<html><body><h1>Hello from Rocket Web Server</h1></body></html>'
return [data]
def demo():
from optparse import OptionParser
parser = OptionParser()
parser.add_option("-i", "--ip", dest="ip",default="127.0.0.1",
help="ip address of the network interface")
parser.add_option("-p", "--port", dest="port",default="8000",
help="post where to run web server")
parser.add_option("-s", "--static", dest="static",default=None,
help="folder containing static files")
(options, args) = parser.parse_args()
global static_folder
static_folder = options.static
print 'Rocket running on %s:%s' % (options.ip, options.port)
r=Rocket((options.ip,int(options.port)),'wsgi', {'wsgi_app':demo_app})
r.start()
if __name__=='__main__':
demo()
+47 -38
View File
@@ -215,7 +215,7 @@ class JobGraph(object):
nested_dict = dict(
(item, (dep - ordered)) for item, dep in nested_dict.items()
if item not in ordered
)
)
assert not nested_dict, "A cyclic dependency exists amongst %r" % nested_dict
db.commit()
return rtn
@@ -297,7 +297,7 @@ def executor(queue, task, out):
f = task.function
functions = current._scheduler.tasks
if not functions:
#look into env
# look into env
_function = _env.get(f)
else:
_function = functions.get(f)
@@ -314,7 +314,7 @@ def executor(queue, task, out):
vars = loads(task.vars, object_hook=_decode_dict)
result = dumps(_function(*args, **vars))
else:
### for testing purpose only
# for testing purpose only
result = eval(task.function)(
*loads(task.args, object_hook=_decode_dict),
**loads(task.vars, object_hook=_decode_dict))
@@ -391,7 +391,6 @@ class MetaScheduler(threading.Thread):
except:
p.terminate()
p.join()
self.have_heartbeat = False
logger.debug(' task stopped by general exception')
tr = TaskReport(STOPPED)
else:
@@ -406,7 +405,6 @@ class MetaScheduler(threading.Thread):
except Queue.Empty:
tr = TaskReport(TIMEOUT)
elif queue.empty():
self.have_heartbeat = False
logger.debug(' task stopped')
tr = TaskReport(STOPPED)
else:
@@ -665,7 +663,7 @@ class Scheduler(MetaScheduler):
Field('traceback', 'text'),
Field('worker_name', default=self.worker_name),
migrate=self.__get_migrate('scheduler_run', migrate)
)
)
db.define_table(
'scheduler_worker',
@@ -677,23 +675,30 @@ class Scheduler(MetaScheduler):
Field('group_names', 'list:string', default=self.group_names),
Field('worker_stats', 'json'),
migrate=self.__get_migrate('scheduler_worker', migrate)
)
)
db.define_table(
'scheduler_task_deps',
Field('job_name', default='job_0'),
Field('task_parent', 'integer',
requires=IS_IN_DB(db, 'scheduler_task.id',
'%(task_name)s')
),
requires=IS_IN_DB(db, 'scheduler_task.id', '%(task_name)s')
),
Field('task_child', 'reference scheduler_task'),
Field('can_visit', 'boolean', default=False),
migrate=self.__get_migrate('scheduler_task_deps', migrate)
)
)
if migrate is not False:
db.commit()
@staticmethod
def total_seconds(td):
# backport for py2.6
if hasattr(td, 'total_seconds'):
return td.total_seconds()
else:
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10.0 ** 6
def loop(self, worker_name=None):
"""Main loop
@@ -718,7 +723,7 @@ class Scheduler(MetaScheduler):
while True and self.have_heartbeat:
if self.w_stats.status == DISABLED:
logger.debug('Someone stopped me, sleeping until better'
' times come (%s)', self.w_stats.sleep)
' times come (%s)', self.w_stats.sleep)
self.sleep()
continue
logger.debug('looping...')
@@ -735,7 +740,8 @@ class Scheduler(MetaScheduler):
logger.debug('sleeping...')
if self.max_empty_runs != 0:
logger.debug('empty runs %s/%s',
self.w_stats.empty_runs, self.max_empty_runs)
self.w_stats.empty_runs,
self.max_empty_runs)
if self.w_stats.empty_runs >= self.max_empty_runs:
logger.info(
'empty runs limit reached, killing myself')
@@ -791,15 +797,15 @@ class Scheduler(MetaScheduler):
now = self.now()
st = self.db.scheduler_task
if self.is_a_ticker and self.do_assign_tasks:
#I'm a ticker, and 5 loops passed without reassigning tasks,
#let's do that and loop again
# I'm a ticker, and 5 loops passed without reassigning tasks,
# let's do that and loop again
self.wrapped_assign_tasks(db)
return None
# ready to process something
grabbed = db(
(st.assigned_worker_name == self.worker_name) &
(st.status == ASSIGNED)
)
)
task = grabbed.select(limitby=(0, 1), orderby=st.next_run_time).first()
if task:
@@ -819,11 +825,15 @@ class Scheduler(MetaScheduler):
if not task.prevent_drift:
next_run_time = task.last_run_time + datetime.timedelta(
seconds=task.period
)
else:
next_run_time = task.start_time + datetime.timedelta(
seconds=task.period * times_run
)
else:
# calc next_run_time based on available slots
# see #1191
next_run_time = task.start_time
secondspassed = self.total_seconds(now - next_run_time)
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)
if times_run < task.repeats or task.repeats == 0:
# need to run (repeating task)
run_again = True
@@ -845,7 +855,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
db.rollback()
logger.info('new task %(id)s "%(task_name)s"'
' %(application_name)s.%(function_name)s' % task)
' %(application_name)s.%(function_name)s' % task)
return Task(
app=task.application_name,
function=task.function_name,
@@ -922,13 +932,13 @@ class Scheduler(MetaScheduler):
else:
st_mapping = {'FAILED': 'FAILED',
'TIMEOUT': 'TIMEOUT',
'STOPPED': 'QUEUED'}[task_report.status]
'STOPPED': 'FAILED'}[task_report.status]
status = (task.retry_failed
and task.times_failed < task.retry_failed
and QUEUED or task.retry_failed == -1
and QUEUED or st_mapping)
db(st.id == task.task_id).update(
times_failed=db.scheduler_task.times_failed + 1,
times_failed=st.times_failed + 1,
next_run_time=task.next_run_time,
status=status
)
@@ -982,7 +992,7 @@ class Scheduler(MetaScheduler):
# keep sleeping
self.w_stats.status = DISABLED
logger.debug('........recording heartbeat (%s)',
self.w_stats.status)
self.w_stats.status)
db(sw.worker_name == self.worker_name).update(
last_heartbeat=now,
worker_stats=self.w_stats)
@@ -999,7 +1009,7 @@ class Scheduler(MetaScheduler):
logger.info('Asked to kill the current task')
self.terminate_process()
logger.debug('........recording heartbeat (%s)',
self.w_stats.status)
self.w_stats.status)
db(sw.worker_name == self.worker_name).update(
last_heartbeat=now, status=ACTIVE,
worker_stats=self.w_stats)
@@ -1025,7 +1035,7 @@ class Scheduler(MetaScheduler):
db(
(st.assigned_worker_name.belongs(dead_workers_name)) &
(st.status == RUNNING)
).update(assigned_worker_name='', status=QUEUED)
).update(assigned_worker_name='', status=QUEUED)
dead_workers.delete()
try:
self.is_a_ticker = self.being_a_ticker()
@@ -1110,20 +1120,20 @@ class Scheduler(MetaScheduler):
(sd.can_visit == False) &
(~sd.task_child.belongs(
db(sd.can_visit == False)._select(sd.task_parent)
)
)
)._select(sd.task_child)
)
)._select(sd.task_child)
no_deps = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
(
(sd.id == None) | (st.id.belongs(deps_with_no_deps))
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
)._select(st.id, distinct=True, left=sd.on(
(st.id == sd.task_parent) &
(sd.can_visit == False)
)
)
all_available = db(
(st.status.belongs((QUEUED, ASSIGNED))) &
@@ -1135,7 +1145,6 @@ class Scheduler(MetaScheduler):
(st.id.belongs(no_deps))
)
limit = len(all_workers) * (50 / (len(wkgroups) or 1))
# if there are a moltitude of tasks, let's figure out a maximum of
# tasks per worker. This can be further tuned with some added
@@ -1179,7 +1188,7 @@ class Scheduler(MetaScheduler):
db(
(st.id == task.id) &
(st.status.belongs((QUEUED, ASSIGNED)))
).update(**d)
).update(**d)
wkgroups[gname]['workers'][myw]['c'] += 1
db.commit()
# I didn't report tasks but I'm working nonetheless!!!!
@@ -1217,7 +1226,7 @@ class Scheduler(MetaScheduler):
self.db(
(ws.group_names.contains(group)) &
(~ws.status.belongs(exclusion))
).update(status=action)
).update(status=action)
else:
for group in group_names:
workers = self.db((ws.group_names.contains(group)) &
@@ -1302,7 +1311,7 @@ class Scheduler(MetaScheduler):
if immediate:
self.db(
(self.db.scheduler_worker.is_ticker == True)
).update(status=PICK)
).update(status=PICK)
else:
rtn.uuid = None
return rtn
@@ -1352,7 +1361,7 @@ class Scheduler(MetaScheduler):
**dict(orderby=orderby,
left=left,
limitby=(0, 1))
).first()
).first()
if row and output:
row.result = row.scheduler_run.run_result and \
loads(row.scheduler_run.run_result,
+40 -10
View File
@@ -27,7 +27,7 @@ from gluon.html import FORM, INPUT, LABEL, OPTION, SELECT, COL, COLGROUP
from gluon.html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE, SCRIPT
from gluon.html import URL, FIELDSET, P, DEFAULT_PASSWORD_DISPLAY
from pydal.base import DEFAULT
from pydal.objects import Table, Row, Expression, Field
from pydal.objects import Table, Row, Expression, Field, Set
from pydal.adapters.base import CALLABLETYPES
from pydal.helpers.methods import smart_query, bar_encode, _repr_ref
from pydal.helpers.classes import Reference, SQLCustomType
@@ -677,7 +677,23 @@ class AutocompleteWidget(object):
def callback(self):
if self.keyword in self.request.vars:
field = self.fields[0]
if settings and settings.global_settings.web2py_runtime_gae:
if type(field) is Field.Virtual:
records = []
table_rows = self.db(self.db[field.tablename]).select(orderby=self.orderby)
count = 0
for row in table_rows:
if self.at_beginning:
if row[field.name].lower().startswith(self.request.vars[self.keyword]):
count += 1
records.append(row)
else:
if self.request.vars[self.keyword] in row[field.name].lower():
count += 1
records.append(row)
if count == 10:
break
rows = Rows(self.db, records, table_rows.colnames, compact=table_rows.compact)
elif settings and settings.global_settings.web2py_runtime_gae:
rows = self.db(field.__ge__(self.request.vars[self.keyword]) & field.__lt__(self.request.vars[self.keyword] + u'\ufffd')).select(orderby=self.orderby, limitby=self.limitby, *(self.fields+self.help_fields))
elif self.at_beginning:
rows = self.db(field.like(self.request.vars[self.keyword] + '%', case_sensitive=False)).select(orderby=self.orderby, limitby=self.limitby, distinct=self.distinct, *(self.fields+self.help_fields))
@@ -725,8 +741,16 @@ class AutocompleteWidget(object):
del attr['requires']
attr['_name'] = key2
value = attr['value']
record = self.db(
self.fields[1] == value).select(self.fields[0]).first()
if type(self.fields[0]) is Field.Virtual:
record = None
table_rows = self.db(self.db[self.fields[0].tablename]).select(orderby=self.orderby)
for row in table_rows:
if row.id == value:
record = row
break
else:
record = self.db(
self.fields[1] == value).select(self.fields[0]).first()
attr['value'] = record and record[self.fields[0].name]
attr['_onblur'] = "jQuery('#%(div_id)s').delay(1000).fadeOut('slow');" % \
dict(div_id=div_id, u='F' + self.keyword)
@@ -877,6 +901,7 @@ def formstyle_bootstrap3_stacked(form, fields):
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(0, ' ')
_controls = DIV(label, _help, _class="checkbox")
label = ''
elif isinstance(controls, (SELECT, TEXTAREA)):
@@ -912,7 +937,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
# wrappers
_help = SPAN(help, _class='help-block')
# embed _help into _controls
_controls = DIV(controls, _help, _class=col_class)
_controls = DIV(controls, _help, _class="%s" % (col_class))
if isinstance(controls, INPUT):
if controls['_type'] == 'submit':
controls.add_class('btn btn-primary')
@@ -926,6 +951,7 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
elif controls['_type'] == 'checkbox':
label['_for'] = None
label.insert(0, controls)
label.insert(1, ' ')
_controls = DIV(DIV(label, _help, _class="checkbox"),
_class="%s %s" % (offset_class, col_class))
label = ''
@@ -938,8 +964,6 @@ def formstyle_bootstrap3_inline_factory(col_label_size=3):
elif isinstance(controls, UL):
for e in controls.elements("input"):
e.add_class('form-control')
elif controls is None or isinstance(controls, basestring):
_controls = P(controls, _class="form-control-static %s" % col_class)
if isinstance(label, LABEL):
label['_class'] = add_class(label.get('_class'),'control-label %s' % label_col_class)
@@ -2014,6 +2038,8 @@ class SQLFORM(FORM):
use_cursor=False):
formstyle = formstyle or current.response.formstyle
if isinstance(query, Set):
query = query.query
# jQuery UI ThemeRoller classes (empty if ui is disabled)
if ui == 'jquery-ui':
@@ -2159,7 +2185,7 @@ class SQLFORM(FORM):
buttonurl=url(args=[]), callback=None,
delete=None, trap=True, noconfirm=None, title=None):
if showbuttontext:
return A(SPAN(_class=ui.get(buttonclass)),
return A(SPAN(_class=ui.get(buttonclass)), CAT(' '),
SPAN(T(buttontext), _title=title or T(buttontext),
_class=ui.get('buttontext')),
_href=buttonurl,
@@ -2337,7 +2363,7 @@ class SQLFORM(FORM):
if deletable(record):
if ondelete:
ondelete(table, request.args[-1])
record.delete_record()
db(table[table._id.name] == request.args[-1]).delete()
if request.ajax:
# this means javascript is enabled, so we don't need to do
# a redirect
@@ -2717,7 +2743,11 @@ class SQLFORM(FORM):
if field.type == 'blob':
continue
if isinstance(field, Field.Virtual) and field.tablename in row:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
try:
# fast path, works for joins
value = row[field.tablename][field.name]
except KeyError:
value = dbset.db[field.tablename][row[field.tablename][field_id]][field.name]
else:
value = row[str(field)]
maxlength = maxtextlengths.get(str(field), maxtextlength)
+4 -4
View File
@@ -25,7 +25,6 @@ regex_stop_range = re.compile('(?<=\-)\d+')
DEFAULT_CHUNK_SIZE = 64 * 1024
def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
offset = 0
while bytes is None or offset < bytes:
@@ -51,11 +50,12 @@ def stream_file_or_304_or_206(
status=200,
error_message=None
):
if error_message is None:
error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
# FIX THIS
# if error_message is None:
# error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request'
try:
open = file # this makes no sense but without it GAE cannot open files
fp = open(static_file)
fp = open(static_file,'rb')
except IOError, e:
if e[0] == errno.EISDIR:
raise HTTP(403, error_message, web2py_error='file is a directory')
+2
View File
@@ -3,12 +3,14 @@ import sys
from test_http import *
from test_cache import *
from test_contenttype import *
from test_compileapp import *
from test_fileutils import *
from test_globals import *
from test_html import *
from test_is_url import *
from test_languages import *
from test_router import *
from test_recfile import *
from test_routes import *
from test_storage import *
from test_serializers import *
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Unit tests for utils.py """
import unittest
from fix_path import fix_sys_path
fix_sys_path(__file__)
from compileapp import compile_application, remove_compiled_application
from gluon.fileutils import w2p_pack, w2p_unpack
import os
class TestPack(unittest.TestCase):
""" Tests the compileapp.py module """
def test_compile(self):
#apps = ['welcome', 'admin', 'examples']
apps = ['welcome']
for appname in apps:
appname_path = os.path.join(os.getcwd(), 'applications', appname)
compile_application(appname_path)
remove_compiled_application(appname_path)
test_path = os.path.join(os.getcwd(), "%s.w2p" % appname)
unpack_path = os.path.join(os.getcwd(), 'unpack', appname)
w2p_pack(test_path, appname_path, compiled=True, filenames=None)
w2p_pack(test_path, appname_path, compiled=False, filenames=None)
w2p_unpack(test_path, unpack_path)
return
if __name__ == '__main__':
unittest.main()
+11 -5
View File
@@ -12,13 +12,19 @@ from fileutils import parse_version
class TestFileUtils(unittest.TestCase):
def testParseVersion(self):
rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26')
self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26)))
rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17')
self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17)))
def test_parse_version(self):
# Legacy
rtn = parse_version('Version 1.99.0 (2011-09-19 08:23:26)')
self.assertEqual(rtn, (1, 99, 0, 'dev', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic
rtn = parse_version('Version 1.99.0-rc.1+timestamp.2011.09.19.08.23.26')
self.assertEqual(rtn, (1, 99, 0, 'rc.1', datetime.datetime(2011, 9, 19, 8, 23, 26)))
# Semantic Stable
rtn = parse_version('Version 2.9.11-stable+timestamp.2014.09.15.18.31.17')
self.assertEqual(rtn, (2, 9, 11, 'stable', datetime.datetime(2014, 9, 15, 18, 31, 17)))
# Semantic Beta
rtn = parse_version('Version 2.14.1-beta+timestamp.2016.03.21.22.35.26')
self.assertEqual(rtn, (2, 14, 1, 'beta', datetime.datetime(2016, 3, 21, 22, 35, 26)))
if __name__ == '__main__':
+590 -319
View File
@@ -12,314 +12,30 @@ fix_sys_path(__file__)
from html import *
from html import verifyURL
from html import truncate_string
from storage import Storage
from html import XML_pickle, XML_unpickle
from html import TAG_pickler, TAG_unpickler
class TestBareHelpers(unittest.TestCase):
def testBR(self):
self.assertEqual(BR(_a='1', _b='2').xml(), '<br a="1" b="2" />')
# xmlescape() = covered by other tests
def testEMBED(self):
self.assertEqual(EMBED(_a='1', _b='2').xml(),
'<embed a="1" b="2" />')
# TODO: def test_call_as_list(self):
def testHR(self):
self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />')
def test_truncate_string(self):
# Ascii text
self.assertEqual(truncate_string('Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
length=30), 'Lorem ipsum dolor sit amet,...')
self.assertEqual(truncate_string('Short text shorter than the length parameter.', length=100),
'Short text shorter than the length parameter.')
# French text
self.assertEqual(truncate_string('Un texte en français avec des accents et des caractères bizarre.', length=30),
'Un texte en français avec d...')
def testIMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
'<img a="1" b="2" />')
def testINPUT(self):
self.assertEqual(INPUT(_a='1', _b='2').xml(),
'<input a="1" b="2" type="text" />')
def testLINK(self):
self.assertEqual(LINK(_a='1', _b='2').xml(),
'<link a="1" b="2" />')
def testMETA(self):
self.assertEqual(META(_a='1', _b='2').xml(),
'<meta a="1" b="2" />')
def testA(self):
self.assertEqual(
A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2">&lt;&gt;</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', delete='tr').xml(),
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>'
)
self.assertEqual(
A('a', _id='b', target='<self>').xml(),
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>'
)
self.assertEqual(
A('a', component='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>'
)
self.assertEqual(
A('a', _id='b', callback='c', noconfirm=True).xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
def testB(self):
self.assertEqual(B('<>', _a='1', _b='2').xml(),
'<b a="1" b="2">&lt;&gt;</b>')
def testBODY(self):
self.assertEqual(BODY('<>', _a='1', _b='2').xml(),
'<body a="1" b="2">&lt;&gt;</body>')
def testCENTER(self):
self.assertEqual(CENTER('<>', _a='1', _b='2').xml(),
'<center a="1" b="2">&lt;&gt;</center>')
def testDIV(self):
self.assertEqual(DIV('<>', _a='1', _b='2').xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# attributes can be updated like in a dict
div = DIV('<>', _a='1')
div['_b'] = '2'
self.assertEqual(div.xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# also with a mapping
div.update(_b=2, _c=3)
self.assertEqual(div.xml(),
'<div a="1" b="2" c="3">&lt;&gt;</div>')
# length of the DIV is the number of components
self.assertEqual(len(DIV('a', 'bc')), 2)
# also if empty, DIV is True in a boolean evaluation
self.assertTrue(True if DIV() else False)
# parent and siblings
a = DIV(SPAN('a'), DIV('b'))
s = a.element('span')
d = s.parent
d['_class'] = 'abc'
self.assertEqual(a.xml(), '<div class="abc"><span>a</span><div>b</div></div>')
self.assertEqual([el.xml() for el in s.siblings()], ['<div>b</div>'])
self.assertEqual(s.sibling().xml(), '<div>b</div>')
self.assertEqual(s.siblings('a'), [])
def testEM(self):
self.assertEqual(EM('<>', _a='1', _b='2').xml(),
'<em a="1" b="2">&lt;&gt;</em>')
def testFIELDSET(self):
self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(),
'<fieldset a="1" b="2">&lt;&gt;</fieldset>')
def testFORM(self):
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
def testH1(self):
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
'<h1 a="1" b="2">&lt;&gt;</h1>')
def testH2(self):
self.assertEqual(H2('<>', _a='1', _b='2').xml(),
'<h2 a="1" b="2">&lt;&gt;</h2>')
def testH3(self):
self.assertEqual(H3('<>', _a='1', _b='2').xml(),
'<h3 a="1" b="2">&lt;&gt;</h3>')
def testH4(self):
self.assertEqual(H4('<>', _a='1', _b='2').xml(),
'<h4 a="1" b="2">&lt;&gt;</h4>')
def testH5(self):
self.assertEqual(H5('<>', _a='1', _b='2').xml(),
'<h5 a="1" b="2">&lt;&gt;</h5>')
def testH6(self):
self.assertEqual(H6('<>', _a='1', _b='2').xml(),
'<h6 a="1" b="2">&lt;&gt;</h6>')
def testHEAD(self):
self.assertEqual(HEAD('<>', _a='1', _b='2').xml(),
'<head a="1" b="2">&lt;&gt;</head>')
def testHTML(self):
self.assertEqual(HTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
def testIFRAME(self):
self.assertEqual(IFRAME('<>', _a='1', _b='2').xml(),
'<iframe a="1" b="2">&lt;&gt;</iframe>')
def testLABEL(self):
self.assertEqual(LABEL('<>', _a='1', _b='2').xml(),
'<label a="1" b="2">&lt;&gt;</label>')
def testLI(self):
self.assertEqual(LI('<>', _a='1', _b='2').xml(),
'<li a="1" b="2">&lt;&gt;</li>')
def testOBJECT(self):
self.assertEqual(OBJECT('<>', _a='1', _b='2').xml(),
'<object a="1" b="2">&lt;&gt;</object>')
def testOL(self):
self.assertEqual(OL('<>', _a='1', _b='2').xml(),
'<ol a="1" b="2"><li>&lt;&gt;</li></ol>')
def testOPTION(self):
self.assertEqual(OPTION('<>', _a='1', _b='2').xml(),
'<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' +
'</option>')
def testP(self):
self.assertEqual(P('<>', _a='1', _b='2').xml(),
'<p a="1" b="2">&lt;&gt;</p>')
# test cr2br
self.assertEqual(P('a\nb').xml(), '<p>a\nb</p>')
self.assertEqual(P('a\nb', cr2br=True).xml(), '<p>a<br />b</p>')
def testPRE(self):
self.assertEqual(PRE('<>', _a='1', _b='2').xml(),
'<pre a="1" b="2">&lt;&gt;</pre>')
def testSCRIPT(self):
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
'''<script a="1" b="2"><!--
<>
//--></script>''')
self.assertEqual(SCRIPT('<>').xml(),
'''<script><!--
<>
//--></script>''')
self.assertEqual(SCRIPT().xml(), '<script></script>')
def testSELECT(self):
self.assertEqual(SELECT('<>', _a='1', _b='2').xml(),
'<select a="1" b="2">' +
'<option value="&lt;&gt;">&lt;&gt;</option></select>')
def testSPAN(self):
self.assertEqual(SPAN('<>', _a='1', _b='2').xml(),
'<span a="1" b="2">&lt;&gt;</span>')
def testSTYLE(self):
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
def testTABLE(self):
self.assertEqual(TABLE('<>', _a='1', _b='2').xml(),
'<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' +
'</table>')
def testTBODY(self):
self.assertEqual(TBODY('<>', _a='1', _b='2').xml(),
'<tbody a="1" b="2"><tr><td>&lt;&gt;</td></tr></tbody>')
def testTD(self):
self.assertEqual(TD('<>', _a='1', _b='2').xml(),
'<td a="1" b="2">&lt;&gt;</td>')
def testTEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
'<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' +
'</textarea>')
# override _rows and _cols
self.assertEqual(TEXTAREA('<>', _a='1', _b='2', _rows=5, _cols=20).xml(),
'<textarea a="1" b="2" cols="20" rows="5">&lt;&gt;' +
'</textarea>')
def testTFOOT(self):
self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(),
'<tfoot a="1" b="2"><tr><td>&lt;&gt;</td></tr></tfoot>')
def testTH(self):
self.assertEqual(TH('<>', _a='1', _b='2').xml(),
'<th a="1" b="2">&lt;&gt;</th>')
def testTHEAD(self):
self.assertEqual(THEAD('<>', _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
#self.assertEqual(THEAD(TRHEAD('<>'), _a='1', _b='2').xml(),
# '<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
self.assertEqual(THEAD(TR('<>'), _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><td>&lt;&gt;</td></tr></thead>')
def testTITLE(self):
self.assertEqual(TITLE('<>', _a='1', _b='2').xml(),
'<title a="1" b="2">&lt;&gt;</title>')
def testTR(self):
self.assertEqual(TR('<>', _a='1', _b='2').xml(),
'<tr a="1" b="2"><td>&lt;&gt;</td></tr>')
def testTT(self):
self.assertEqual(TT('<>', _a='1', _b='2').xml(),
'<tt a="1" b="2">&lt;&gt;</tt>')
def testUL(self):
self.assertEqual(UL('<>', _a='1', _b='2').xml(),
'<ul a="1" b="2"><li>&lt;&gt;</li></ul>')
def testXML(self):
# sanitization process
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
'<h1>Hello<a data-hello="world">World</a></h1>')
# with sanitize, data-attributes are not permitted
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).xml(),
'<h1>HelloWorld</h1>')
# stringify by default
self.assertEqual(XML(1.3), '1.3')
self.assertEqual(XML(u'<div>è</div>').xml(), '<div>\xc3\xa8</div>')
# you can calc len on the class, that equals the xml() and the str()
self.assertEqual(len(XML('1.3')), len('1.3'))
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
self.assertEqual(len(str(XML('1.3'))), len('1.3'))
# you can concatenate them to strings (check for __add__ and __radd__ methods)
self.assertEqual(XML('a') + 'b', 'ab')
self.assertEqual(XML('a') + XML('b'), 'ab')
self.assertEqual('a' + XML('b'), 'ab')
# you can compare them
self.assertEqual(XML('a') == XML('a'), True)
# beware that the comparison is made on the XML repr
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
#bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
def testTAG(self):
self.assertEqual(TAG.first(TAG.second('test'), _key=3).xml(),
'<first key="3"><second>test</second></first>')
# ending in underscore "triggers" <input /> style
self.assertEqual(TAG.first_(TAG.second('test'), _key=3).xml(),
'<first key="3" />')
def testStaticURL(self):
def test_StaticURL(self):
# test response.static_version coupled with response.static_version_urls
self.assertEqual(URL('a', 'c', 'f'), '/a/c/f')
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/design.css')
@@ -331,62 +47,62 @@ class TestBareHelpers(unittest.TestCase):
response.static_version_urls = True
self.assertEqual(URL('a', 'static', 'design.css'), '/a/static/_1.2.3/design.css')
def testURL(self):
def test_URL(self):
self.assertEqual(URL('a', 'c', 'f', args='1'), '/a/c/f/1')
self.assertEqual(URL('a', 'c', 'f', args=('1', '2')), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', 'f', args=['1', '2']), '/a/c/f/1/2')
self.assertEqual(URL('a', 'c', '/f'), '/a/c/f')
self.assertEqual(URL('a', 'c', 'f.json'), '/a/c/f.json')
self.assertRaises(SyntaxError, URL, *['a'])
request = Storage()
request.application = 'a'
request.controller = 'c'
request.function = 'f'
request.env = {}
from globals import current
from globals import current # Can't be moved with other import
current.request = request
must_return = '/a/c/f'
self.assertEqual(URL(), must_return)
self.assertEqual(URL('f'), must_return)
self.assertEqual(URL('c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f'), must_return)
self.assertEqual(URL('a', 'c', 'f', extension='json'), '/a/c/f.json')
def weird():
pass
self.assertEqual(URL('a', 'c', weird), '/a/c/weird')
self.assertRaises(SyntaxError, URL, *['a', 'c', 1])
# test signature
rtn = URL(
a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2}, anchor='1', hmac_key='key'
)
rtn = URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2}, anchor='1', hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1')
# test _signature exclusion
rtn = URL(
a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2, '_signature': 'abc'},
anchor='1', hmac_key='key'
)
rtn = URL(a='a', c='c', f='f', args=['x', 'y', 'z'],
vars={'p': (1, 3), 'q': 2, '_signature': 'abc'},
anchor='1', hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f#1')
# emulate user_signature
current.session = Storage(auth=Storage(hmac_key='key'))
self.assertEqual(URL(user_signature=True), '/a/c/f?_signature=c4aed53c08cff08f369dbf8b5ba51889430cf2c2')
# hash_vars combination
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=True)
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=True)
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=a32530f0d0caa80964bb92aad2bedf8a4486a31f')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=False)
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=False)
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=0b5a0702039992aad23c82794b8496e5dcd59a5b')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars=['p'])
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars=['p'])
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
rtn = URL('a','c','f', args=['x', 'y', 'z'], vars={'p' : (1,3), 'q' : 2}, hmac_key='key', hash_vars='p')
rtn = URL('a', 'c', 'f', args=['x', 'y', 'z'], vars={'p': (1, 3), 'q': 2}, hmac_key='key', hash_vars='p')
self.assertEqual(rtn, '/a/c/f/x/y/z?p=1&p=3&q=2&_signature=5d01b982fd72b39674b012e0288071034e156d7a')
# test CRLF detection
self.assertRaises(SyntaxError, URL, *['a\n', 'c', 'f'])
self.assertRaises(SyntaxError, URL, *['a\r', 'c', 'f'])
def testverifyURL(self):
def test_verifyURL(self):
r = Storage()
r.application = 'a'
r.controller = 'c'
@@ -429,12 +145,567 @@ class TestBareHelpers(unittest.TestCase):
rtn = verifyURL(r, user_signature=True)
self.assertEqual(rtn, True)
# TODO: def test_XmlComponent(self):
def test_XML(self):
# sanitization process
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>').xml(),
'<h1>Hello<a data-hello="world">World</a></h1>')
# with sanitize, data-attributes are not permitted
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True).xml(),
'<h1>HelloWorld</h1>')
# stringify by default
self.assertEqual(XML(1.3), '1.3')
self.assertEqual(XML(u'<div>è</div>').xml(), '<div>\xc3\xa8</div>')
# you can calc len on the class, that equals the xml() and the str()
self.assertEqual(len(XML('1.3')), len('1.3'))
self.assertEqual(len(XML('1.3').xml()), len('1.3'))
self.assertEqual(len(str(XML('1.3'))), len('1.3'))
# you can concatenate them to strings (check for __add__ and __radd__ methods)
self.assertEqual(XML('a') + 'b', 'ab')
self.assertEqual(XML('a') + XML('b'), 'ab')
self.assertEqual('a' + XML('b'), 'ab')
# you can compare them
self.assertEqual(XML('a') == XML('a'), True)
# beware that the comparison is made on the XML repr
self.assertEqual(XML('<h1>Hello<a data-hello="world">World</a></h1>', sanitize=True),
XML('<h1>HelloWorld</h1>'))
# bug check for the sanitizer for closing no-close tags
self.assertEqual(XML('<p>Test</p><br/><p>Test</p><br/>', sanitize=True),
XML('<p>Test</p><br /><p>Test</p><br />'))
def test_XML_pickle_unpickle(self):
# weird test
self.assertEqual(XML_unpickle(XML_pickle('data to be pickle')[1][0]), 'data to be pickle')
def test_DIV(self):
# Empty DIV()
self.assertEqual(DIV().xml(), '<div></div>')
self.assertEqual(DIV('<>', _a='1', _b='2').xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# attributes can be updated like in a dict
div = DIV('<>', _a='1')
div['_b'] = '2'
self.assertEqual(div.xml(),
'<div a="1" b="2">&lt;&gt;</div>')
# also with a mapping
div.update(_b=2, _c=3)
self.assertEqual(div.xml(),
'<div a="1" b="2" c="3">&lt;&gt;</div>')
# length of the DIV is the number of components
self.assertEqual(len(DIV('a', 'bc')), 2)
# also if empty, DIV is True in a boolean evaluation
self.assertTrue(True if DIV() else False)
# parent and siblings
a = DIV(SPAN('a'), DIV('b'))
s = a.element('span')
d = s.parent
d['_class'] = 'abc'
self.assertEqual(a.xml(), '<div class="abc"><span>a</span><div>b</div></div>')
self.assertEqual([el.xml() for el in s.siblings()], ['<div>b</div>'])
self.assertEqual(s.sibling().xml(), '<div>b</div>')
# siblings with wrong args
self.assertEqual(s.siblings('a'), [])
# siblings with good args
self.assertEqual(s.siblings('div')[0].xml(), '<div>b</div>')
# Check for siblings with wrong kargs and value
self.assertEqual(s.siblings(a='d'), [])
# Check for siblings with good kargs and value
# Can't figure this one out what is a right value here??
# Commented for now...
# self.assertEqual(s.siblings(div='<div>b</div>'), ???)
# No other sibling should return None
self.assertEqual(DIV(P('First element')).element('p').sibling(), None)
# --------------------------------------------------------------------------------------------------------------
# This use unicode to hit xmlescape() line :
# """
# elif isinstance(data, unicode):
# data = data.encode('utf8', 'xmlcharrefreplace')
# """
self.assertEqual(DIV(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
# --------------------------------------------------------------------------------------------------------------
self.assertEqual(DIV('Test with an ID', _id='id-of-the-element').xml(),
'<div id="id-of-the-element">Test with an ID</div>')
self.assertEqual(DIV().element('p'), None)
# Corner case for raise coverage of one line
# I think such assert fail cause of python 2.6
# Work under python 2.7
# with self.assertRaises(SyntaxError) as cm:
# DIV(BR('<>')).xml()
# self.assertEqual(cm.exception[0], '<br/> tags cannot have components')
def test_CAT(self):
# Empty CAT()
self.assertEqual(CAT().xml(), '')
# CAT('')
self.assertEqual(CAT('').xml(), '')
# CAT(' ')
self.assertEqual(CAT(' ').xml(), ' ')
def test_TAG_pickler_unpickler(self):
# weird test
self.assertEqual(TAG_unpickler(TAG_pickler(TAG.div('data to be pickle'))[1][0]).xml(),
'<div>data to be pickle</div>')
def test_TAG(self):
self.assertEqual(TAG.first(TAG.second('test'), _key=3).xml(),
'<first key="3"><second>test</second></first>')
# ending in underscore "triggers" <input /> style
self.assertEqual(TAG.first_(TAG.second('test'), _key=3).xml(),
'<first key="3" />')
# unicode test for TAG
self.assertEqual(TAG.div(u'Texte en français avec des caractères accentués...').xml(),
'<div>Texte en fran\xc3\xa7ais avec des caract\xc3\xa8res accentu\xc3\xa9s...</div>')
def test_HTML(self):
self.assertEqual(HTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='strict').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='transitional').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='frameset').xml(),
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='html5').xml(),
'<!DOCTYPE HTML>\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='').xml(),
'<html a="1" b="2" lang="en">&lt;&gt;</html>')
self.assertEqual(HTML('<>', _a='1', _b='2', doctype='CustomDocType').xml(),
'CustomDocType\n<html a="1" b="2" lang="en">&lt;&gt;</html>')
def test_XHTML(self):
# Empty XHTML test
self.assertEqual(XHTML().xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"></html>')
# Not Empty XHTML test
self.assertEqual(XHTML('<>', _a='1', _b='2').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='strict').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='transitional').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='frameset').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', doctype='xmlns').xml(),
'xmlns\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
self.assertEqual(XHTML('<>', _a='1', _b='2', _xmlns='xmlns').xml(),
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n<html a="1" b="2" lang="en" xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">&lt;&gt;</html>')
def test_HEAD(self):
self.assertEqual(HEAD('<>', _a='1', _b='2').xml(),
'<head a="1" b="2">&lt;&gt;</head>')
def test_TITLE(self):
self.assertEqual(TITLE('<>', _a='1', _b='2').xml(),
'<title a="1" b="2">&lt;&gt;</title>')
def test_META(self):
self.assertEqual(META(_a='1', _b='2').xml(),
'<meta a="1" b="2" />')
def test_LINK(self):
self.assertEqual(LINK(_a='1', _b='2').xml(),
'<link a="1" b="2" />')
def test_SCRIPT(self):
self.assertEqual(SCRIPT('<>', _a='1', _b='2').xml(),
'''<script a="1" b="2"><!--
<>
//--></script>''')
self.assertEqual(SCRIPT('<>').xml(),
'''<script><!--
<>
//--></script>''')
self.assertEqual(SCRIPT().xml(), '<script></script>')
def test_STYLE(self):
self.assertEqual(STYLE('<>', _a='1', _b='2').xml(),
'<style a="1" b="2"><!--/*--><![CDATA[/*><!--*/\n<>\n/*]]>*/--></style>')
# Try to hit : return DIV.xml(self)
self.assertEqual(STYLE().xml(), '<style></style>')
def test_IMG(self):
self.assertEqual(IMG(_a='1', _b='2').xml(),
'<img a="1" b="2" />')
def test_SPAN(self):
self.assertEqual(SPAN('<>', _a='1', _b='2').xml(),
'<span a="1" b="2">&lt;&gt;</span>')
def test_BODY(self):
self.assertEqual(BODY('<>', _a='1', _b='2').xml(),
'<body a="1" b="2">&lt;&gt;</body>')
def test_H1(self):
self.assertEqual(H1('<>', _a='1', _b='2').xml(),
'<h1 a="1" b="2">&lt;&gt;</h1>')
def test_H2(self):
self.assertEqual(H2('<>', _a='1', _b='2').xml(),
'<h2 a="1" b="2">&lt;&gt;</h2>')
def test_H3(self):
self.assertEqual(H3('<>', _a='1', _b='2').xml(),
'<h3 a="1" b="2">&lt;&gt;</h3>')
def test_H4(self):
self.assertEqual(H4('<>', _a='1', _b='2').xml(),
'<h4 a="1" b="2">&lt;&gt;</h4>')
def test_H5(self):
self.assertEqual(H5('<>', _a='1', _b='2').xml(),
'<h5 a="1" b="2">&lt;&gt;</h5>')
def test_H6(self):
self.assertEqual(H6('<>', _a='1', _b='2').xml(),
'<h6 a="1" b="2">&lt;&gt;</h6>')
def test_P(self):
self.assertEqual(P('<>', _a='1', _b='2').xml(),
'<p a="1" b="2">&lt;&gt;</p>')
# test cr2br
self.assertEqual(P('a\nb').xml(), '<p>a\nb</p>')
self.assertEqual(P('a\nb', cr2br=True).xml(), '<p>a<br />b</p>')
def test_STRONG(self):
self.assertEqual(STRONG('<>', _a='1', _b='2').xml(),
'<strong a="1" b="2">&lt;&gt;</strong>')
def test_B(self):
self.assertEqual(B('<>', _a='1', _b='2').xml(),
'<b a="1" b="2">&lt;&gt;</b>')
def test_BR(self):
# empty BR()
self.assertEqual(BR().xml(), '<br />')
self.assertEqual(BR(_a='1', _b='2').xml(), '<br a="1" b="2" />')
def test_HR(self):
self.assertEqual(HR(_a='1', _b='2').xml(), '<hr a="1" b="2" />')
def test_A(self):
self.assertEqual(
A('<>', _a='1', _b='2').xml(),
'<a a="1" b="2">&lt;&gt;</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="c">a</a>'
)
# Callback with no id trigger web2py_uuid() call
from html import web2pyHTMLParser
a = A('a', callback='b').xml()
for tag in web2pyHTMLParser(a).tree.elements('a'):
uuid_generated = tag.attributes['_id']
self.assertEqual(a,
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="b" id="{id}">a</a>'.format(id=uuid_generated))
self.assertEqual(
A('a', delete='tr').xml(),
'<a data-w2p_disable_with="default" data-w2p_remove="tr">a</a>'
)
self.assertEqual(
A('a', _id='b', target='<self>').xml(),
'<a data-w2p_disable_with="default" data-w2p_target="b" id="b">a</a>'
)
self.assertEqual(
A('a', component='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" href="b">a</a>'
)
self.assertEqual(
A('a', _id='b', callback='c', noconfirm=True).xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" href="c" id="b">a</a>'
)
self.assertEqual(
A('a', cid='b').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', cid='b', _disable_with='processing...').xml(),
'<a data-w2p_disable_with="processing..." data-w2p_method="GET" data-w2p_target="b">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', noconfirm=True, _id='c').xml(),
'<a data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
self.assertEqual(
A('a', callback='b', delete='tr', confirm='Are you sure?', _id='c').xml(),
'<a data-w2p_confirm="Are you sure?" data-w2p_disable_with="default" data-w2p_method="POST" data-w2p_remove="tr" href="b" id="c">a</a>'
)
def test_BUTTON(self):
self.assertEqual(BUTTON('test', _type='button').xml(),
'<button type="button">test</button>')
def test_EM(self):
self.assertEqual(EM('<>', _a='1', _b='2').xml(),
'<em a="1" b="2">&lt;&gt;</em>')
def test_EMBED(self):
self.assertEqual(EMBED(_a='1', _b='2').xml(),
'<embed a="1" b="2" />')
def test_TT(self):
self.assertEqual(TT('<>', _a='1', _b='2').xml(),
'<tt a="1" b="2">&lt;&gt;</tt>')
def test_PRE(self):
self.assertEqual(PRE('<>', _a='1', _b='2').xml(),
'<pre a="1" b="2">&lt;&gt;</pre>')
def test_CENTER(self):
self.assertEqual(CENTER('<>', _a='1', _b='2').xml(),
'<center a="1" b="2">&lt;&gt;</center>')
def test_CODE(self):
self.assertEqual(CODE("print 'hello world'",
language='python',
link=None,
counter=1,
styles={},
highlight_line=None).xml(),
'<table><tr style="vertical-align:top;"><td style="min-width:40px; text-align: right;"><pre style="\n font-size: 11px;\n font-family: Bitstream Vera Sans Mono,monospace;\n background-color: transparent;\n margin: 0;\n padding: 5px;\n border: none;\n color: #A0A0A0;\n">1.</pre></td><td><pre style="\n font-size: 11px;\n font-family: Bitstream Vera Sans Mono,monospace;\n background-color: transparent;\n margin: 0;\n padding: 5px;\n border: none;\n overflow: auto;\n white-space: pre !important;\n"><span style="color:#185369; font-weight: bold">print </span><span style="color: #FF9966">\'hello world\'</span></pre></td></tr></table>')
def test_LABEL(self):
self.assertEqual(LABEL('<>', _a='1', _b='2').xml(),
'<label a="1" b="2">&lt;&gt;</label>')
def test_LI(self):
self.assertEqual(LI('<>', _a='1', _b='2').xml(),
'<li a="1" b="2">&lt;&gt;</li>')
def test_UL(self):
self.assertEqual(UL('<>', _a='1', _b='2').xml(),
'<ul a="1" b="2"><li>&lt;&gt;</li></ul>')
def test_OL(self):
self.assertEqual(OL('<>', _a='1', _b='2').xml(),
'<ol a="1" b="2"><li>&lt;&gt;</li></ol>')
def test_TD(self):
self.assertEqual(TD('<>', _a='1', _b='2').xml(),
'<td a="1" b="2">&lt;&gt;</td>')
def test_TH(self):
self.assertEqual(TH('<>', _a='1', _b='2').xml(),
'<th a="1" b="2">&lt;&gt;</th>')
def test_TR(self):
self.assertEqual(TR('<>', _a='1', _b='2').xml(),
'<tr a="1" b="2"><td>&lt;&gt;</td></tr>')
def test_THEAD(self):
self.assertEqual(THEAD('<>', _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
# self.assertEqual(THEAD(TRHEAD('<>'), _a='1', _b='2').xml(),
# '<thead a="1" b="2"><tr><th>&lt;&gt;</th></tr></thead>')
self.assertEqual(THEAD(TR('<>'), _a='1', _b='2').xml(),
'<thead a="1" b="2"><tr><td>&lt;&gt;</td></tr></thead>')
def test_TBODY(self):
self.assertEqual(TBODY('<>', _a='1', _b='2').xml(),
'<tbody a="1" b="2"><tr><td>&lt;&gt;</td></tr></tbody>')
def test_TFOOT(self):
self.assertEqual(TFOOT('<>', _a='1', _b='2').xml(),
'<tfoot a="1" b="2"><tr><td>&lt;&gt;</td></tr></tfoot>')
def test_COL(self):
# Empty COL test
self.assertEqual(COL().xml(), '<col />')
# Not Empty COL test
self.assertEqual(COL(_span='2').xml(), '<col span="2" />')
# Commented for now not so sure how to make it pass properly was passing locally
# I think this test is interesting and add value
# This fail relate to python 2.6 limitation I think
# Failing COL test
# with self.assertRaises(SyntaxError) as cm:
# COL('<>').xml()
# self.assertEqual(cm.exception[0], '<col/> tags cannot have components')
# For now
self.assertRaises(SyntaxError, COL, '<>')
def test_COLGROUP(self):
# Empty COLGROUP test
self.assertEqual(COLGROUP().xml(), '<colgroup></colgroup>')
# Not Empty COLGROUP test
self.assertEqual(COLGROUP('<>', _a='1', _b='2').xml(), '<colgroup a="1" b="2">&lt;&gt;</colgroup>')
def test_TABLE(self):
self.assertEqual(TABLE('<>', _a='1', _b='2').xml(),
'<table a="1" b="2"><tr><td>&lt;&gt;</td></tr>' +
'</table>')
def test_I(self):
self.assertEqual(I('<>', _a='1', _b='2').xml(),
'<i a="1" b="2">&lt;&gt;</i>')
def test_IFRAME(self):
self.assertEqual(IFRAME('<>', _a='1', _b='2').xml(),
'<iframe a="1" b="2">&lt;&gt;</iframe>')
def test_INPUT(self):
self.assertEqual(INPUT(_a='1', _b='2').xml(), '<input a="1" b="2" type="text" />')
# list value
self.assertEqual(INPUT(_value=[1, 2, 3]).xml(), '<input type="text" value="[1, 2, 3]" />')
def test_TEXTAREA(self):
self.assertEqual(TEXTAREA('<>', _a='1', _b='2').xml(),
'<textarea a="1" b="2" cols="40" rows="10">&lt;&gt;' +
'</textarea>')
# override _rows and _cols
self.assertEqual(TEXTAREA('<>', _a='1', _b='2', _rows=5, _cols=20).xml(),
'<textarea a="1" b="2" cols="20" rows="5">&lt;&gt;' +
'</textarea>')
self.assertEqual(TEXTAREA('<>', value='bla bla bla...', _rows=10, _cols=40).xml(),
'<textarea cols="40" rows="10">bla bla bla...</textarea>')
def test_OPTION(self):
self.assertEqual(OPTION('<>', _a='1', _b='2').xml(),
'<option a="1" b="2" value="&lt;&gt;">&lt;&gt;' +
'</option>')
def test_OBJECT(self):
self.assertEqual(OBJECT('<>', _a='1', _b='2').xml(),
'<object a="1" b="2">&lt;&gt;</object>')
def test_OPTGROUP(self):
# Empty OPTGROUP test
self.assertEqual(OPTGROUP().xml(),
'<optgroup></optgroup>')
# Not Empty OPTGROUP test
self.assertEqual(OPTGROUP('<>', _a='1', _b='2').xml(),
'<optgroup a="1" b="2"><option value="&lt;&gt;">&lt;&gt;</option></optgroup>')
# With an OPTION
self.assertEqual(OPTGROUP(OPTION('Option 1', _value='1'), _label='Group 1').xml(),
'<optgroup label="Group 1"><option value="1">Option 1</option></optgroup>')
def test_SELECT(self):
self.assertEqual(SELECT('<>', _a='1', _b='2').xml(),
'<select a="1" b="2">' +
'<option value="&lt;&gt;">&lt;&gt;</option></select>')
self.assertEqual(SELECT(OPTION('option 1', _value='1'),
OPTION('option 2', _value='2')).xml(),
'<select><option value="1">option 1</option><option value="2">option 2</option></select>')
self.assertEqual(SELECT(OPTION('option 1', _value='1', _selected='selected'),
OPTION('option 2', _value='2'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option value="2">option 2</option></select>')
# More then one select with mutilple
self.assertEqual(SELECT(OPTION('option 1', _value='1', _selected='selected'),
OPTION('option 2', _value='2', _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option selected="selected" value="1">option 1</option><option selected="selected" value="2">option 2</option></select>'
)
# OPTGROUP
self.assertEqual(SELECT(OPTGROUP(OPTION('option 1', _value='1'),
OPTION('option 2', _value='2'),
_label='Group 1',)).xml(),
'<select><optgroup label="Group 1"><option value="1">option 1</option><option value="2">option 2</option></optgroup></select>')
# List
self.assertEqual(SELECT([1, 2, 3, 4, 5]).xml(),
'<select><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option></select>')
# Tuple
self.assertEqual(SELECT((1, 2, 3, 4, 5)).xml(),
'<select><option value="1">1</option><option value="2">2</option><option value="3">3</option><option value="4">4</option><option value="5">5</option></select>')
# String value
self.assertEqual(SELECT('Option 1', 'Option 2').xml(),
'<select><option value="Option 1">Option 1</option><option value="Option 2">Option 2</option></select>')
# list as a value
self.assertEqual(SELECT(OPTION('option 1', _value=[1, 2, 3]),
OPTION('option 2', _value=[4, 5, 6], _selected='selected'),
_multiple='multiple').xml(),
'<select multiple="multiple"><option value="[1, 2, 3]">option 1</option><option selected="selected" value="[4, 5, 6]">option 2</option></select>')
def test_FIELDSET(self):
self.assertEqual(FIELDSET('<>', _a='1', _b='2').xml(),
'<fieldset a="1" b="2">&lt;&gt;</fieldset>')
def test_LEGEND(self):
self.assertEqual(LEGEND('<>', _a='1', _b='2').xml(),
'<legend a="1" b="2">&lt;&gt;</legend>')
def test_FORM(self):
self.assertEqual(FORM('<>', _a='1', _b='2').xml(),
'<form a="1" action="#" b="2" enctype="multipart/form-data" method="post">&lt;&gt;</form>')
# These 2 crash AppVeyor and Travis with: "ImportError: No YAML serializer available"
# self.assertEqual(FORM('<>', _a='1', _b='2').as_yaml(),
# "accepted: null\nattributes: {_a: '1', _action: '#', _b: '2', _enctype: multipart/form-data, _method: post}\ncomponents: [<>]\nerrors: {}\nlatest: {}\nparent: null\nvars: {}\n")
# self.assertEqual(FORM('<>', _a='1', _b='2').as_xml(),
# '<?xml version="1.0" encoding="UTF-8"?><document><errors></errors><vars></vars><parent>None</parent><attributes><_enctype>multipart/form-data</_enctype><_action>#</_action><_b>2</_b><_a>1</_a><_method>post</_method></attributes><components><item>&amp;lt;&amp;gt;</item></components><accepted>None</accepted><latest></latest></document>')
def test_BEAUTIFY(self):
self.assertEqual(BEAUTIFY(['a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
# unicode
self.assertEqual(BEAUTIFY([P(u'àéèûôç'), 'a', 'b', {'hello': 'world'}]).xml(),
'<div><table><tr><td><div><p>\xc3\xa0\xc3\xa9\xc3\xa8\xc3\xbb\xc3\xb4\xc3\xa7</p></div></td></tr><tr><td><div>a</div></td></tr><tr><td><div>b</div></td></tr><tr><td><div><table><tr><td style="font-weight:bold;vertical-align:top;">hello</td><td style="vertical-align:top;">:</td><td><div>world</div></td></tr></table></div></td></tr></table></div>')
def test_MENU(self):
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li></ul>')
# Multiples entries menu
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])]).xml(),
'<ul class="web2py-menu web2py-menu-vertical"><li class="web2py-menu-first"><a href="/welcome/default/index">Home</a></li><li><a href="/welcome/default/func_one">Item 1</a></li><li><a href="/welcome/default/func_two">Item 2</a></li><li><a href="/welcome/default/func_three">Item 3</a></li><li class="web2py-menu-last"><a href="/welcome/default/func_four">Item 4</a></li></ul>'
)
# mobile=True
self.assertEqual(MENU([('Home', False, '/welcome/default/index', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option></select>')
# Multiples entries menu for mobile
self.assertEqual(MENU([('Home', False, '/welcome/default/index', []),
('Item 1', False, '/welcome/default/func_one', []),
('Item 2', False, '/welcome/default/func_two', []),
('Item 3', False, '/welcome/default/func_three', []),
('Item 4', False, '/welcome/default/func_four', [])], mobile=True).xml(),
'<select class="web2py-menu web2py-menu-vertical" onchange="window.location=this.value"><option value="/welcome/default/index">Home</option><option value="/welcome/default/func_one">Item 1</option><option value="/welcome/default/func_two">Item 2</option><option value="/welcome/default/func_three">Item 3</option><option value="/welcome/default/func_four">Item 4</option></select>')
# TODO: def test_embed64(self):
# TODO: def test_web2pyHTMLParser(self):
# TODO: def test_markdown_serializer(self):
# TODO: def test_markmin_serializer(self):
def test_MARKMIN(self):
# This test pass with python 2.7 but expected to fail under 2.6
# with self.assertRaises(TypeError) as cm:
# MARKMIN().xml()
# self.assertEqual(cm.exception[0], '__init__() takes at least 2 arguments (1 given)')
# For now
self.assertRaises(TypeError, MARKMIN)
self.assertEqual(MARKMIN('').xml(), '')
self.assertEqual(MARKMIN('<>').xml(),
'<p>&lt;&gt;</p>')
self.assertEqual(MARKMIN("``hello_world = 'Hello World!'``:python").xml(),
'<code class="python">hello_world = \'Hello World!\'</code>')
self.assertEqual(MARKMIN('<>').flatten(), '<>')
def test_ASSIGNJS(self):
# empty assignation
self.assertEqual(ASSIGNJS().xml(), '')
# text assignation
self.assertEqual(ASSIGNJS(var1='1', var2='2').xml(), 'var var1 = "1";\nvar var2 = "2";\n')
# int assignation
self.assertEqual(ASSIGNJS(var1=1, var2=2).xml(), 'var var1 = 1;\nvar var2 = 2;\n')
class TestData(unittest.TestCase):
def testAdata(self):
def test_Adata(self):
self.assertEqual(A('<>', data=dict(abc='<def?asd>', cde='standard'), _a='1', _b='2').xml(),
'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard">&lt;&gt;</a>')
'<a a="1" b="2" data-abc="&lt;def?asd&gt;" data-cde="standard">&lt;&gt;</a>')
if __name__ == '__main__':
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for gluon.recfile
"""
import unittest
import os
import shutil
import uuid
from fix_path import fix_sys_path
fix_sys_path(__file__)
from gluon import recfile
class TestRecfile(unittest.TestCase):
def setUp(self):
os.mkdir('tests')
def tearDown(self):
shutil.rmtree('tests')
def testgeneration(self):
for k in range(20):
teststring = 'test%s' % k
filename = os.path.join('tests', str(uuid.uuid4()) + '.test')
with recfile.open(filename, "w") as g:
g.write(teststring)
self.assertEqual(recfile.open(filename, "r").read(), teststring)
is_there = recfile.exists(filename)
self.assertTrue(is_there)
recfile.remove(filename)
is_there = recfile.exists(filename)
self.assertFalse(is_there)
if __name__ == '__main__':
unittest.main()
+3
View File
@@ -0,0 +1,3 @@
# TODO : I think we should continue to use pathoc (http://pathod.net/docs/pathoc) for tests but integrate the call in
# gluon/tests so they run automatically. No need to make our own tests.
# ref: https://groups.google.com/d/msg/web2py-developers/Cjye8_hXZk8/AXbftS3sCgAJ
+141 -2
View File
@@ -6,6 +6,7 @@
"""
import os
import sys
import smtplib
if sys.version < "2.7":
import unittest2 as unittest
else:
@@ -19,7 +20,7 @@ DEFAULT_URI = os.getenv('DB', 'sqlite:memory')
from gluon.dal import DAL, Field
from pydal.objects import Table
from tools import Auth
from tools import Auth, Mail
from gluon.globals import Request, Response, Session
from storage import Storage
from languages import translator
@@ -29,7 +30,7 @@ python_version = sys.version[:3]
IS_IMAP = "imap" in DEFAULT_URI
@unittest.skipIf(IS_IMAP, "TODO: Imap raises 'Connection refused'")
class testAuth(unittest.TestCase):
class TestAuth(unittest.TestCase):
def testRun(self):
# setup
@@ -79,5 +80,143 @@ class testAuth(unittest.TestCase):
pass
return
class TestMail(unittest.TestCase):
"""
Test the Mail class.
"""
class Message(object):
def __init__(self, sender, to, payload):
self.sender = sender
self.to = to
self.payload = payload
class DummySMTP(object):
"""
Dummy smtp server
NOTE: Test methods should take care of always leaving inbox and users empty when they finish.
"""
inbox = []
users = {}
def __init__(self, address, port, **kwargs):
self.address=address
self.port = port
self.has_quit = False
self.tls = False
def login(self, username, password):
if username not in self.users or self.users[username] != password:
raise smtplib.SMTPAuthenticationError
self.username=username
self.password=password
def sendmail(self, sender, to, payload):
self.inbox.append(TestMail.Message(sender, to, payload))
def quit(self):
self.has_quit=True
def ehlo(self, hostname=None):
pass
def starttls(self):
self.tls = True
def setUp(self):
self.original_SMTP = smtplib.SMTP
self.original_SMTP_SSL = smtplib.SMTP_SSL
smtplib.SMTP = TestMail.DummySMTP
smtplib.SMTP_SSL = TestMail.DummySMTP
def tearDown(self):
smtplib.SMTP = self.original_SMTP
smtplib.SMTP_SSL = self.original_SMTP_SSL
def test_hello_world(self):
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
message = TestMail.DummySMTP.inbox.pop()
self.assertEqual(message.sender, mail.settings.sender)
self.assertEqual(message.to, ['somebody@example.com'])
header = "To: somebody@example.com\nReply-To: us@example.com\nSubject: hello\n"
self.assertTrue(header in message.payload)
self.assertTrue(message.payload.endswith('world'))
def test_failed_login(self):
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
self.assertFalse(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
def test_login(self):
TestMail.DummySMTP.users['username'] = 'password'
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.login = 'username:password'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
del TestMail.DummySMTP.users['username']
TestMail.DummySMTP.inbox.pop()
def test_html(self):
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='<html><head></head><body></body></html>'))
message = TestMail.DummySMTP.inbox.pop()
self.assertTrue('Content-Type: text/html' in message.payload)
def test_ssl(self):
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.ssl = True
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
TestMail.DummySMTP.inbox.pop()
def test_tls(self):
mail = Mail()
mail.settings.server = 'smtp.example.com:25'
mail.settings.sender = 'you@example.com'
mail.settings.tls = True
self.assertTrue(mail.send(to=['somebody@example.com'],
subject='hello',
# If reply_to is omitted, then mail.settings.sender is used
reply_to='us@example.com',
message='world'))
TestMail.DummySMTP.inbox.pop()
if __name__ == '__main__':
unittest.main()
+100 -31
View File
@@ -10,20 +10,18 @@ fix_sys_path(__file__)
from utils import md5_hash
from utils import compare
from utils import is_valid_ip_address
from utils import web2py_uuid
import hashlib
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
from utils import simple_hash, get_digest
from utils import simple_hash, get_digest, secure_dumps, secure_loads
class TestUtils(unittest.TestCase):
""" Tests the utils.py module """
def test_md5_hash(self):
""" Tests the md5_hash function """
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
# TODO: def test_AES_new(self):
def test_compare(self):
""" Tests the compare funciton """
@@ -36,56 +34,127 @@ class TestUtils(unittest.TestCase):
compare_result_false = compare(a, b)
self.assertFalse(compare_result_false)
def test_md5_hash(self):
""" Tests the md5_hash function """
data = md5_hash("web2py rocks")
self.assertEqual(data, '79509f3246a2824dee64635303e99204')
def test_simple_hash(self):
""" Tests the simple_hash function """
# no key, no salt, md5
# no key, no salt, digest_alg=None
self.assertRaises(RuntimeError, simple_hash, 'web2py rocks!', key='', salt='', digest_alg=None)
# no key, no salt, digest_alg = md5
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg=md5)
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, 'md5'
data_md5 = simple_hash('web2py rocks!', key='', salt='', digest_alg='md5')
self.assertEqual(data_md5, '37d95defba6c8834cb8cae86ee888568')
# no key, no salt, sha1
# no key, no salt, 'sha1'
data_sha1 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha1')
self.assertEqual(data_sha1, '00489a46753d8db260c71542611cdef80652c4b7')
# no key, no salt, sha224
# no key, no salt, 'sha224'
data_sha224 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha224')
self.assertEqual(data_sha224, '84d7054271842c2c17983baa2b1447e0289d101140a8c002d49d60da')
# no key, no salt, sha256
# no key, no salt, 'sha256'
data_sha256 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha256')
self.assertEqual(data_sha256, '0849f224d8deb267e4598702aaec1bd749e6caec90832469891012a4be24af08')
# no key, no salt, sha384
# no key, no salt, 'sha384'
data_sha384 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha384')
self.assertEqual(data_sha384,
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
'3cffaf39371adbe84eb10f588d2718207d8e965e9172a27a278321b86977351376ae79f92e91d8c58cad86c491282d5f')
# no key, no salt, sha512
# no key, no salt, 'sha512'
data_sha512 = simple_hash('web2py rocks!', key='', salt='', digest_alg='sha512')
self.assertEqual(data_sha512, 'fa3237f594743e1d7b6c800bb134b3255cf4a98ab8b01e2ec23256328c9f8059'
'64fdef25a038d6cc3fda1b2fb45d66461eeed5c4669e506ec8bdfee71348db7e')
# NOTE : get_digest() is covered by simple_hash tests above except raise error...
def test_get_digest(self):
# Bad algorithm
# Option 1, think not working with python 2.6
# with self.assertRaises(ValueError) as cm:
# get_digest('123')
# self.assertEqual(cm.exception[0], 'Invalid digest algorithm: 123')
# Option 2
self.assertRaises(ValueError, get_digest, '123')
# TODO: def test_get_callable_argspec(self):
class TestPack(unittest.TestCase):
""" Tests the compileapp.py module """
# TODO: def test_pad(self):
def test_secure_dumps_and_loads(self):
""" Tests secure_dumps and secure_loads"""
testobj = {'a': 1, 'b': 2}
testkey = 'mysecret'
secured = secure_dumps(testobj, testkey)
original = secure_loads(secured, testkey)
self.assertEqual(testobj, original)
self.assertTrue(isinstance(secured, basestring))
self.assertTrue(':' in secured)
large_testobj = [x for x in range(1000)]
secured_comp = secure_dumps(large_testobj, testkey, compression_level=9)
original_comp = secure_loads(secured_comp, testkey, compression_level=9)
self.assertEqual(large_testobj, original_comp)
secured = secure_dumps(large_testobj, testkey)
self.assertTrue(len(secured_comp) < len(secured))
testhash = 'myhash'
secured = secure_dumps(testobj, testkey, testhash)
original = secure_loads(secured, testkey, testhash)
self.assertEqual(testobj, original)
wrong1 = secure_loads(secured, testkey, 'wronghash')
self.assertEqual(wrong1, None)
wrong2 = secure_loads(secured, 'wrongkey', testhash)
self.assertEqual(wrong2, None)
wrong3 = secure_loads(secured, 'wrongkey', 'wronghash')
self.assertEqual(wrong3, None)
wrong4 = secure_loads('abc', 'a', 'b')
self.assertEqual(wrong4, None)
# TODO: def test_initialize_urandom(self):
# TODO: def test_fast_urandom16(self):
def test_web2py_uuid(self):
from uuid import UUID
self.assertTrue(UUID(web2py_uuid()))
def test_is_valid_ip_address(self):
# IPv4
# False
# self.assertEqual(is_valid_ip_address('127.0'), False) # Fail with AppVeyor?? should pass
self.assertEqual(is_valid_ip_address('unknown'), False)
self.assertEqual(is_valid_ip_address(''), False)
# True
self.assertEqual(is_valid_ip_address('127.0.0.1'), True)
self.assertEqual(is_valid_ip_address('localhost'), True)
self.assertEqual(is_valid_ip_address('::1'), True)
# IPv6
# True
# Compressed
self.assertEqual(is_valid_ip_address('::ffff:7f00:1'), True) # IPv6 127.0.0.1 compressed
self.assertEqual(is_valid_ip_address('2001:660::1'), True)
# Expanded
self.assertEqual(is_valid_ip_address('0:0:0:0:0:ffff:7f00:1'), True) # IPv6 127.0.0.1 expanded
self.assertEqual(is_valid_ip_address('2607:fa48:6d50:69f1:21f:3cff:fe9d:9be3'), True) # Any address
# False
# self.assertEqual(is_valid_ip_address('2607:fa48:6d50:69f1:21f:3cff:fe9d:'), False) # Any address with mistake
# The above pass locally but fail with AppVeyor
# TODO: def test_is_loopback_ip_address(self):
# TODO: def test_getipaddrinfo(self):
def test_compile(self):
from compileapp import compile_application, remove_compiled_application
from gluon.fileutils import w2p_pack, w2p_unpack
import os
#apps = ['welcome', 'admin', 'examples']
apps = ['welcome']
for appname in apps:
appname_path = os.path.join(os.getcwd(), 'applications', appname)
compile_application(appname_path)
remove_compiled_application(appname_path)
test_path = os.path.join(os.getcwd(), "%s.w2p" % appname)
unpack_path = os.path.join(os.getcwd(), 'unpack', appname)
w2p_pack(test_path, appname_path, compiled=True, filenames=None)
w2p_pack(test_path, appname_path, compiled=False, filenames=None)
w2p_unpack(test_path, unpack_path)
return
if __name__ == '__main__':
unittest.main()
File diff suppressed because it is too large Load Diff
+11 -9
View File
@@ -22,20 +22,21 @@ from urllib2 import HTTPError
webserverprocess = None
def startwebserver():
global webserverprocess
path = path = os.path.dirname(os.path.abspath(__file__))
if not os.path.isfile(os.path.join(path,'web2py.py')):
if not os.path.isfile(os.path.join(path, 'web2py.py')):
i = 0
while i<10:
while i < 10:
i += 1
if os.path.exists(os.path.join(path,'web2py.py')):
if os.path.exists(os.path.join(path, 'web2py.py')):
break
path = os.path.abspath(os.path.join(path, '..'))
web2py_exec = os.path.join(path, 'web2py.py')
webserverprocess = subprocess.Popen([sys.executable, web2py_exec, '-a', 'testpass'])
print 'Sleeping before web2py starts...'
for a in range(1,11):
for a in range(1, 11):
time.sleep(1)
print a, '...'
try:
@@ -46,10 +47,11 @@ def startwebserver():
continue
print ''
def terminate_process(pid):
#Taken from http://stackoverflow.com/questions/1064335/in-python-2-5-how-do-i-kill-a-subprocess
# Taken from http://stackoverflow.com/questions/1064335/in-python-2-5-how-do-i-kill-a-subprocess
# all this **blah** is because we are stuck with Python 2.5 and \
#we cannot use Popen.terminate()
# we cannot use Popen.terminate()
if sys.platform.startswith('win'):
import ctypes
PROCESS_TERMINATE = 1
@@ -59,10 +61,11 @@ def terminate_process(pid):
else:
os.kill(pid, signal.SIGKILL)
def stopwebserver():
global webserverprocess
print 'Killing webserver'
if sys.version_info < (2,6):
if sys.version_info < (2, 6):
terminate_process(webserverprocess.pid)
else:
webserverprocess.terminate()
@@ -108,7 +111,6 @@ class TestWeb(LiveTest):
# check registration and login were successful
client.get('index')
# COMMENTED BECAUSE FAILS BUT WHY?
self.assertTrue('Welcome Homer' in client.text)
client = WebClient('http://127.0.0.1:8000/admin/default/')
@@ -153,7 +155,7 @@ class TestWeb(LiveTest):
try:
s.post('examples/soap_examples/call/soap', data=xml_request, method="POST")
except HTTPError, e:
assert(e.msg=='INTERNAL SERVER ERROR')
assert(e.msg == 'INTERNAL SERVER ERROR')
# check internal server error returned (issue 153)
assert(s.status == 500)
assert(s.text == xml_response)
-1
View File
@@ -1 +0,0 @@
+84 -70
View File
@@ -23,6 +23,7 @@ import glob
import os
import re
import time
import fnmatch
import traceback
import smtplib
import urllib
@@ -34,13 +35,12 @@ import email.utils
import random
import hmac
import hashlib
import json
from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string, Charset
from gluon.serializers import json_parser
from gluon.contenttype import contenttype
from gluon.storage import Storage, StorageList, Settings, Messages
from gluon.utils import web2py_uuid
from gluon.utils import web2py_uuid, compare
from gluon.fileutils import read_file, check_credentials
from gluon import *
from gluon.contrib.autolinks import expand_one
@@ -53,17 +53,6 @@ import gluon.serializers as serializers
Table = DAL.Table
Field = DAL.Field
try:
# try stdlib (Python 2.6)
import json as json_parser
except ImportError:
try:
# try external module
import simplejson as json_parser
except:
# fallback to pure-Python module
import gluon.contrib.simplejson as json_parser
__all__ = ['Mail', 'Auth', 'Recaptcha', 'Recaptcha2', 'Crud', 'Service', 'Wiki',
'PluginManager', 'fetch', 'geocode', 'reverse_geocode', 'prettydate']
@@ -265,7 +254,7 @@ class Mail(object):
MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
self.set_payload(payload)
self['Content-Disposition'] = 'attachment; filename="%s"' % filename
if not content_id is None:
if content_id is not None:
self['Content-Id'] = '<%s>' % content_id.encode(encoding)
Encoders.encode_base64(self)
@@ -789,16 +778,16 @@ class Mail(object):
if attachments:
result = mail.send_mail(
sender=sender, to=origTo,
subject=unicode(subject), body=unicode(text), html=html,
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html,
attachments=attachments, **xcc)
elif html and (not raw):
result = mail.send_mail(
sender=sender, to=origTo,
subject=unicode(subject), body=unicode(text), html=html, **xcc)
subject=unicode(subject, encoding), body=unicode(text, encoding), html=html, **xcc)
else:
result = mail.send_mail(
sender=sender, to=origTo,
subject=unicode(subject), body=unicode(text), **xcc)
subject=unicode(subject, encoding), body=unicode(text, encoding), **xcc)
else:
smtp_args = self.settings.server.split(':')
kwargs = dict(timeout=self.settings.timeout)
@@ -1143,8 +1132,7 @@ def addrow(form, a, b, c, style, _id, position=-1):
class AuthJWT(object):
"""
If left externally, this needs the usual "singleton" approach.
Given I (we) don't know if to include in auth yet, let's stick to basics.
Experimental!
Args:
- secret_key: the secret. Without salting, an attacker knowing this can impersonate
@@ -1175,7 +1163,9 @@ class AuthJWT(object):
return payload
- before_authorization: can be a callable that takes the deserialized token (a dict) as input.
Gets called right after signature verification but before the actual
authorization takes place. You can raise with HTTP a proper error message
authorization takes place. It may be use to cast
the extra auth_user fields to their actual types.
You can raise with HTTP a proper error message
Example:
def mybefore_authorization(tokend):
if not tokend['my_name_is'] == 'bond,james bond':
@@ -1192,8 +1182,8 @@ class AuthJWT(object):
def login_and_take_token():
return myjwt.jwt_token_manager()
A call then to /app/controller/login_and_take_token/auth with username and password returns the token
A call to /app/controller/login_and_take_token/refresh with the original token returns the refreshed token
A call then to /app/controller/login_and_take_token with username and password returns the token
A call to /app/controller/login_and_take_token with the original token returns the refreshed token
To protect a function with JWT
@@ -1204,7 +1194,7 @@ class AuthJWT(object):
"""
def __init__(self,
def __init__(self,
auth,
secret_key,
algorithm='HS256',
@@ -1256,7 +1246,7 @@ class AuthJWT(object):
@staticmethod
def jwt_b64e(string):
if isinstance(string, unicode):
string = string.encode('uft-8', 'strict')
string = string.encode('utf-8', 'strict')
return base64.urlsafe_b64encode(string).strip(b'=')
@staticmethod
@@ -1279,7 +1269,7 @@ class AuthJWT(object):
if isinstance(secret, unicode):
secret = secret.encode('ascii', 'ignore')
b64h = self.cached_b64h
b64p = self.jwt_b64e(json_parser.dumps(payload))
b64p = self.jwt_b64e(serializers.json(payload))
jbody = b64h + '.' + b64p
mauth = hmac.new(key=secret, msg=jbody, digestmod=self.digestmod)
jsign = self.jwt_b64e(mauth.digest())
@@ -1287,7 +1277,7 @@ class AuthJWT(object):
def verify_signature(self, body, signature, secret):
mauth = hmac.new(key=secret, msg=body, digestmod=self.digestmod)
return hmac.compare_digest(self.jwt_b64e(mauth.digest()), signature)
return compare(self.jwt_b64e(mauth.digest()), signature)
def load_token(self, token):
if isinstance(token, unicode):
@@ -1298,7 +1288,7 @@ class AuthJWT(object):
# header not the same
raise HTTP(400, u'Invalid JWT Header')
secret = self.secret_key
tokend = json_parser.loads(self.jwt_b64d(b64b))
tokend = serializers.loads_json(self.jwt_b64d(b64b))
if self.salt:
if callable(self.salt):
secret = "%s$%s" % (secret, self.salt(tokend))
@@ -1324,7 +1314,10 @@ class AuthJWT(object):
We (mis)use the heavy default auth mechanism to avoid any further computation,
while sticking to a somewhat-stable Auth API.
"""
now = time.mktime(datetime.datetime.utcnow().timetuple())
## is the following safe or should we use
## calendar.timegm(datetime.datetime.utcnow().timetuple())
## result seem to be the same (seconds since epoch, in UTC)
now = time.mktime(datetime.datetime.now().timetuple())
expires = now + self.expiration
payload = dict(
hmac_key=session_auth['hmac_key'],
@@ -1336,7 +1329,7 @@ class AuthJWT(object):
return payload
def refresh_token(self, orig_payload):
now = time.mktime(datetime.datetime.utcnow().timetuple())
now = time.mktime(datetime.datetime.now().timetuple())
if self.verify_expiration:
orig_exp = orig_payload['exp']
if orig_exp + self.leeway < now:
@@ -1346,7 +1339,7 @@ class AuthJWT(object):
if orig_iat + self.refresh_expiration_delta < now:
# refreshed too long ago
raise HTTP(400, u'Token issued too long ago')
expires = now + self.refresh_expiration_delta
expires = now + self.expiration
orig_payload.update(
orig_iat=orig_iat,
iat=now,
@@ -1372,14 +1365,17 @@ class AuthJWT(object):
def api_auth():
return myjwt.jwt_token_manager()
Then, a call to /app/c/api_auth/auth with username and password
returns a token, while /app/c/api_auth/refresh with the current token
Then, a call to /app/c/api_auth with username and password
returns a token, while /app/c/api_auth with the current token
issues another token
"""
request = current.request
response = current.response
session = current.session
# forget and unlock response
session.forget(response)
valid_user = None
ret = None
if request.vars.token:
if not self.allow_refresh:
raise HTTP(403, u'Refreshing token is not allowed')
@@ -1387,24 +1383,23 @@ class AuthJWT(object):
tokend = self.load_token(token)
# verification can fail here
refreshed = self.refresh_token(tokend)
ret = {'token':self.generate_token(refreshed)}
ret = {'token': self.generate_token(refreshed)}
elif self.user_param in request.vars and self.pass_param in request.vars:
session.forget(response)
username = request.vars[self.user_param]
password = request.vars[self.pass_param]
valid_user = self.auth.login_bare(username, password)
if valid_user:
payload = self.serialize_auth_session(current.session.auth)
self.alter_payload(payload)
ret = {'token':self.generate_token(payload)}
else:
raise HTTP(
401, u'Not Authorized',
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
else:
raise HTTP(400, u'Must pass token for refresh or username and password for login')
response.headers['content-type'] = 'application/json'
return json.dumps(ret)
valid_user = self.auth.user
if valid_user:
payload = self.serialize_auth_session(current.session.auth)
self.alter_payload(payload)
ret = {'token': self.generate_token(payload)}
elif ret is None:
raise HTTP(
401, u'Not Authorized - need to be logged in, to pass a token for refresh or username and password for login',
**{'WWW-Authenticate': u'JWT realm="%s"' % self.realm})
response.headers['Content-Type'] = 'application/json'
return serializers.json(ret)
def inject_token(self, tokend):
"""
@@ -1716,17 +1711,36 @@ class Auth(object):
args = []
if vars is None:
vars = {}
host = scheme and self.settings.host
return URL(c=self.settings.controller,
f=f, args=args, vars=vars, scheme=scheme)
f=f, args=args, vars=vars, scheme=scheme, host=host)
def here(self):
return URL(args=current.request.args, vars=current.request.get_vars)
def select_host(self, host, host_names=None):
"""
checks that host is valid, i.e. in the list of glob host_names
if the host is missing, then is it selects the first entry from host_names
read more here: https://github.com/web2py/web2py/issues/1196
"""
if host:
if host_names:
for item in host_names:
if fnmatch.fnmatch(host, item):
break
else:
raise HTTP(403, "Invalid Hostname")
elif host_names:
host = host_names[0]
else:
host = 'localhost'
def __init__(self, environment=None, db=None, mailer=True,
hmac_key=None, controller='default', function='user',
cas_provider=None, signature=True, secure=False,
csrf_prevention=True, propagate_extension=None,
url_index=None, jwt=None):
url_index=None, jwt=None, host_names=None):
## next two lines for backward compatibility
if not db and environment and isinstance(environment, DAL):
@@ -1769,9 +1783,10 @@ class Auth(object):
# ## what happens after registration?
settings = self.settings = Settings()
settings.update(Auth.default_settings)
settings.update(Auth.default_settings)
host = self.select_host(request.env.http_host, host_names)
settings.update(
cas_domains=[request.env.http_host],
cas_domains=[host],
enable_tokens=False,
cas_provider=cas_provider,
cas_actions=dict(login='login',
@@ -1821,6 +1836,7 @@ class Auth(object):
label_separator=current.response.form_label_separator,
two_factor_methods = [],
two_factor_onvalidation = [],
host = host,
)
settings.lock_keys = True
# ## these are messages that can be customized
@@ -1846,7 +1862,6 @@ class Auth(object):
self.define_signature()
else:
self.signature = None
self.jwt_handler = jwt and AuthJWT(self, **jwt)
def get_vars_next(self):
@@ -1931,7 +1946,7 @@ class Auth(object):
elif args(1) == self.settings.cas_actions['proxyvalidate']:
return self.cas_validate(version=2, proxy=True)
elif args(1) == self.settings.cas_actions['logout']:
return self.logout(next=request.vars.service or DEFAULT)
return self.logout(next=request.vars.service or DEFAULT)
else:
raise HTTP(404)
@@ -2563,8 +2578,8 @@ class Auth(object):
if not 'first_name' in keys and 'first_name' in table_user.fields:
guess = keys.get('email', 'anonymous').split('@')[0]
keys['first_name'] = keys.get('username', guess)
form = table_user._filter_fields(keys)
user_id = table_user.insert(**form)
vars = table_user._filter_fields(keys)
user_id = table_user.insert(**vars)
user = table_user[user_id]
if self.settings.create_user_groups:
group_id = self.add_group(
@@ -2575,7 +2590,7 @@ class Auth(object):
if login:
self.user = user
if self.settings.register_onaccept:
callback(self.settings.register_onaccept, form)
callback(self.settings.register_onaccept, Storage(vars=user))
return user
def basic(self, basic_auth_realm=False):
@@ -2956,7 +2971,7 @@ class Auth(object):
user = table_user(**{username: entered_username})
if user:
# user in db, check if registration pending or disabled
temp_user = user
temp_user = user
if (temp_user.registration_key or '').startswith('pending'):
response.flash = self.messages.registration_pending
return form
@@ -3301,7 +3316,7 @@ class Auth(object):
if self.settings.register_verify_password:
if self.settings.register_fields is None:
self.settings.register_fields = [f.name for f in table_user if f.writable]
k = self.settings.register_fields.index("password")
k = self.settings.register_fields.index(passfield)
self.settings.register_fields.insert(k+1, "password_two")
extra_fields = [
Field("password_two", "password",
@@ -3759,7 +3774,7 @@ class Auth(object):
except Exception:
session.flash = self.messages.invalid_reset_password
redirect(next, client_side=self.settings.client_side)
key = user.registration_key
if key in ('pending', 'disabled', 'blocked') or (key or '').startswith('pending'):
session.flash = self.messages.registration_pending
@@ -3858,7 +3873,7 @@ class Auth(object):
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
user = table_user(**{userfield:form.vars.get(userfield)})
key = user.registration_key
key = user.registration_key
if not user:
session.flash = self.messages['invalid_%s' % userfield]
redirect(self.url(args=request.args),
@@ -4045,18 +4060,18 @@ class Auth(object):
def jwt(self):
"""
To use JWT authentication:
1) instantiate auth with
1) instantiate auth with::
auth = Auth(db, jwt = {'secret_key':'secret'})
where 'secret' is your own secret string.
where 'secret' is your own secret string.
2) Secorate functions that require login but should accept the JWT token credentials:
2) Decorate functions that require login but should accept the JWT token credentials::
@auth.allows_jwt()
@auth.requires_login()
def myapi(): return 'hello %s' % auth.user.email
Notice jwt is allowed but not required. if user is logged in, myapi is accessible.
3) Use it!
@@ -4074,7 +4089,7 @@ class Auth(object):
Authorization: Bearer <the jwt token>
Any additional attributes in the jwt argument of Auth() below:
Any additional attributes in the jwt argument of Auth() below::
auth = Auth(db, jwt = {...})
@@ -4083,9 +4098,9 @@ class Auth(object):
if not self.jwt_handler:
raise HTTP(400, "Not authorized")
else:
current.response.headers['content-type'] = 'application/json'
raise HTTP(200, self.jwt_handler.jwt_token_manager())
rtn = self.jwt_handler.jwt_token_manager()
raise HTTP(200, rtn, cookies=None, **current.response.headers)
def is_impersonating(self):
return self.is_logged_in() and 'impersonator' in current.session.auth
@@ -4188,7 +4203,7 @@ class Auth(object):
if not self.jwt_handler:
raise HTTP(400, "Not authorized")
else:
return self.jwt_handler.allows_jwt()
return self.jwt_handler.allows_jwt(otherwise=otherwise)
def requires(self, condition, requires_login=True, otherwise=None):
"""
@@ -4201,7 +4216,6 @@ class Auth(object):
basic_allowed, basic_accepted, user = self.basic()
user = user or self.user
login_required = requires_login
if callable(login_required):
login_required = login_required()
@@ -4210,7 +4224,7 @@ class Auth(object):
if not user:
if current.request.ajax:
raise HTTP(401, self.messages.ajax_failed_authentication)
elif not otherwise is None:
elif otherwise is not None:
if callable(otherwise):
return otherwise()
redirect(otherwise)
@@ -4248,7 +4262,7 @@ class Auth(object):
return self.requires(True, otherwise=otherwise)
def requires_login_or_token(self, otherwise=None):
if self.settings.enable_tokens == True:
if self.settings.enable_tokens is True:
user = None
request = current.request
token = request.env.http_web2py_user_token or request.vars._token
+11 -3
View File
@@ -64,6 +64,10 @@ else:
except (ImportError, ValueError):
HAVE_PBKDF2 = False
HAVE_COMPARE_DIGEST = False
if hasattr(hmac, 'compare_digest'):
HAVE_COMPARE_DIGEST = True
logger = logging.getLogger("web2py")
@@ -77,6 +81,8 @@ def AES_new(key, IV=None):
def compare(a, b):
""" Compares two strings and not vulnerable to timing attacks """
if HAVE_COMPARE_DIGEST:
return hmac.compare_digest(a, b)
if len(a) != len(b):
return False
result = 0
@@ -143,6 +149,7 @@ DIGEST_ALG_BY_SIZE = {
512 / 4: 'sha512',
}
def get_callable_argspec(fn):
if inspect.isfunction(fn) or inspect.ismethod(fn):
inspectable = fn
@@ -154,6 +161,7 @@ def get_callable_argspec(fn):
inspectable = fn
return inspect.getargspec(inspectable)
def pad(s, n=32, padchar=' '):
return s + (32 - len(s) % 32) * padchar
@@ -164,7 +172,7 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
dump = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
if compression_level:
dump = zlib.compress(dump, compression_level)
key = pad(encryption_key[:32])
key = pad(encryption_key)[:32]
cipher, IV = AES_new(key)
encrypted_data = base64.urlsafe_b64encode(IV + cipher.encrypt(pad(dump)))
signature = hmac.new(hash_key, encrypted_data).hexdigest()
@@ -172,7 +180,7 @@ def secure_dumps(data, encryption_key, hash_key=None, compression_level=None):
def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
if not ':' in data:
if ':' not in data:
return None
if not hash_key:
hash_key = sha1(encryption_key).hexdigest()
@@ -180,7 +188,7 @@ def secure_loads(data, encryption_key, hash_key=None, compression_level=None):
actual_signature = hmac.new(hash_key, encrypted_data).hexdigest()
if not compare(signature, actual_signature):
return None
key = pad(encryption_key[:32])
key = pad(encryption_key)[:32]
encrypted_data = base64.urlsafe_b64decode(encrypted_data)
IV, encrypted_data = encrypted_data[:16], encrypted_data[16:]
cipher, _ = AES_new(key, IV=IV)
+15 -29
View File
@@ -141,7 +141,6 @@ class Validator(object):
def __call__(self, value):
raise NotImplementedError
return (value, None)
class IS_MATCH(Validator):
@@ -201,12 +200,15 @@ class IS_MATCH(Validator):
def __call__(self, value):
if self.is_unicode:
if isinstance(value,unicode):
match = self.regex.search(value)
else:
if not isinstance(value, unicode):
match = self.regex.search(str(value).decode('utf8'))
else:
match = self.regex.search(value)
else:
match = self.regex.search(str(value))
if not isinstance(value, unicode):
match = self.regex.search(str(value))
else:
match = self.regex.search(value.encode('utf8'))
if match is not None:
return (self.extract and match.group() or value, None)
return (value, translate(self.error_message))
@@ -630,12 +632,11 @@ class IS_IN_DB(Validator):
if self.field.type in ('id','integer'):
new_values = []
for value in values:
if isinstance(value,(int,long)) or value.isdigit():
value = int(value)
elif self.auto_add:
value = self.maybe_add(table, self.fieldnames[0], value)
else:
return (values, translate(self.error_message))
if not (isinstance(value,(int,long)) or value.isdigit()):
if self.auto_add:
value = str(self.maybe_add(table, self.fieldnames[0], value))
else:
return (values, translate(self.error_message))
new_values.append(value)
values = new_values
@@ -2244,7 +2245,7 @@ class IS_DATE(Validator):
y = '%.4i' % year
format = format.replace('%y', y[-2:])
format = format.replace('%Y', y)
if year < 1900:
if year < 1900:
year = 2000
d = datetime.date(year, value.month, value.day)
return d.strftime(format)
@@ -2640,8 +2641,8 @@ class IS_EMPTY_OR(Validator):
if hasattr(other, 'options'):
self.options = self._options
def _options(self):
options = self.other.options()
def _options(self, *args, **kwargs):
options = self.other.options(*args, **kwargs)
if (not options or options[0][0] != '') and not self.multiple:
options.insert(0, ('', ''))
return options
@@ -3061,21 +3062,6 @@ class IS_STRONG(object):
return (value, translate(self.error_message))
class IS_IN_SUBSET(IS_IN_SET):
REGEX_W = re.compile('\w+')
def __init__(self, *a, **b):
IS_IN_SET.__init__(self, *a, **b)
def __call__(self, value):
values = self.REGEX_W.findall(str(value))
failures = [x for x in values if IS_IN_SET.__call__(self, x)[1]]
if failures:
return (value, translate(self.error_message))
return (value, None)
class IS_IMAGE(Validator):
"""
Checks if file uploaded through file input was saved in one of selected
+27 -21
View File
@@ -17,6 +17,7 @@ import time
import thread
import threading
import os
import copy
import socket
import signal
import math
@@ -150,7 +151,7 @@ class web2pyDialog(object):
self.scheduler_processes = {}
self.menu = Tkinter.Menu(self.root)
servermenu = Tkinter.Menu(self.menu, tearoff=0)
httplog = os.path.join(self.options.folder, 'httpserver.log')
httplog = os.path.join(self.options.folder, self.options.log_filename)
iconphoto = os.path.join('extras', 'icons', 'web2py.gif')
if os.path.exists(iconphoto):
img = Tkinter.PhotoImage(file=iconphoto)
@@ -225,9 +226,9 @@ class web2pyDialog(object):
text=str(ProgramVersion + "\n" + ProgramAuthor),
font=('Helvetica', 11), justify=Tkinter.CENTER,
foreground='#195866', background=bg_color,
height=3).pack( side='top',
fill='both',
expand='yes')
height=3).pack(side='top',
fill='both',
expand='yes')
self.bannerarea.after(1000, self.update_canvas)
@@ -322,11 +323,15 @@ class web2pyDialog(object):
self.tb = None
def update_schedulers(self, start=False):
applications_folder = os.path.join(self.options.folder, 'applications')
apps = []
available_apps = [arq for arq in os.listdir('applications/')]
available_apps = [arq for arq in available_apps
if os.path.exists(
'applications/%s/models/scheduler.py' % arq)]
##FIXME - can't start scheduler in the correct dir from Tk
if self.options.folder:
return
available_apps = [
arq for arq in os.listdir(applications_folder)
if os.path.exists(os.path.join(applications_folder, arq, 'models', 'scheduler.py'))
]
if start:
# the widget takes care of starting the scheduler
if self.options.scheduler and self.options.with_scheduler:
@@ -414,9 +419,11 @@ class web2pyDialog(object):
def connect_pages(self):
""" Connects pages """
# reset the menu
available_apps = [arq for arq in os.listdir('applications/')
if os.path.exists(
'applications/%s/__init__.py' % arq)]
applications_folder = os.path.join(self.options.folder, 'applications')
available_apps = [
arq for arq in os.listdir(applications_folder)
if os.path.exists(os.path.join(applications_folder, arq, '__init__.py'))
]
self.pagesmenu.delete(0, len(available_apps))
for arq in available_apps:
url = self.url + arq
@@ -552,14 +559,15 @@ class web2pyDialog(object):
def update_canvas(self):
""" Updates canvas """
httplog = os.path.join(self.options.folder, self.options.log_filename)
try:
t1 = os.path.getsize('httpserver.log')
t1 = os.path.getsize(httplog)
except:
self.canvas.after(1000, self.update_canvas)
return
try:
fp = open('httpserver.log', 'r')
fp = open(httplog, 'r')
fp.seek(self.t0)
data = fp.read(t1 - self.t0)
fp.close()
@@ -933,7 +941,10 @@ def console():
sys.argv, other_args = sys.argv[:k], sys.argv[k + 1:]
(options, args) = parser.parse_args()
options.args = [options.run] + other_args
global_settings.cmd_options = options
copy_options = copy.deepcopy(options)
copy_options.password = '******'
global_settings.cmd_options = copy_options
global_settings.cmd_args = args
if options.gae:
@@ -1051,6 +1062,8 @@ def start_schedulers(options):
apps = options.scheduler_groups
code = "from gluon import current;current._scheduler.loop()"
logging.getLogger().setLevel(options.debuglevel)
if options.folder:
os.chdir(options.folder)
if len(apps) == 1 and not options.with_scheduler:
app_, code = get_code_for_scheduler(apps[0], options)
if not app_:
@@ -1117,13 +1130,6 @@ def start(cron=True):
if hasattr(options, key):
setattr(options, key, getattr(options2, key))
logfile0 = os.path.join('examples', 'logging.example.conf')
if not os.path.exists('logging.conf') and os.path.exists(logfile0):
import shutil
sys.stdout.write("Copying logging.conf.example to logging.conf ... ")
shutil.copyfile(logfile0, 'logging.conf')
sys.stdout.write('OK\n')
# ## if -T run doctests (no cron)
if hasattr(options, 'test') and options.test:
test(options.test, verbose=options.verbose)
-16
View File
@@ -1,16 +0,0 @@
import time
import sys
import urllib2
import urllib2
n = int(sys.argv[1])
url = sys.argv[2]
headers = {"Accept-Language": "en"}
req = urllib2.Request(url, None, headers)
t0 = time.time()
for k in xrange(n):
data = urllib2.urlopen(req).read()
print (time.time() - t0) / n
if n == 1:
print data
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import re
filename = sys.argv[1]
datafile = open(filename, 'r')
try:
data = '\n' + datafile.read()
finally:
datafile.close()
SPACE = '\n ' if '-n' in sys.argv[1:] else ' '
data = re.compile('(?<!\:)//(?P<a>.*)').sub('/* \g<a> */', data)
data = re.compile('[ ]+').sub(' ', data)
data = re.compile('\s*{\s*').sub(' {' + SPACE, data)
data = re.compile('\s*;\s*').sub(';' + SPACE, data)
data = re.compile(',\s*').sub(', ', data)
data = re.compile('\s*\*/\s*').sub('*/' + SPACE, data)
data = re.compile('\s*}\s*').sub(SPACE + '}\n', data)
data = re.compile('\n\s*\n').sub('\n', data)
data = re.compile(';\s+/\*').sub('; /*', data)
data = re.compile('\*/\s+/\*').sub(' ', data)
data = re.compile('[ ]+\n').sub('\n', data)
data = re.compile('\n\s*/[\*]+(?P<a>.*?)[\*]+/', re.DOTALL).sub(
'\n/*\g<a>*/\n', data)
data = re.compile('[ \t]+(?P<a>\S.+?){').sub(' \g<a>{', data)
data = data.replace('}', '}\n')
print data
-67
View File
@@ -1,67 +0,0 @@
import sys
import re
def cleancss(text):
text = re.compile('\s+').sub(' ', text)
text = re.compile('\s*(?P<a>,|:)\s*').sub('\g<a> ', text)
text = re.compile('\s*;\s*').sub(';\n ', text)
text = re.compile('\s*\{\s*').sub(' {\n ', text)
text = re.compile('\s*\}\s*').sub('\n}\n\n', text)
return text
def cleanhtml(text):
text = text.lower()
r = re.compile('\<script.+?/script\>', re.DOTALL)
scripts = r.findall(text)
text = r.sub('<script />', text)
r = re.compile('\<style.+?/style\>', re.DOTALL)
styles = r.findall(text)
text = r.sub('<style />', text)
text = re.compile(
'<(?P<tag>(input|meta|link|hr|br|img|param))(?P<any>[^\>]*)\s*(?<!/)>')\
.sub('<\g<tag>\g<any> />', text)
text = text.replace('\n', ' ')
text = text.replace('>', '>\n')
text = text.replace('<', '\n<')
text = re.compile('\s*\n\s*').sub('\n', text)
lines = text.split('\n')
(indent, newlines) = (0, [])
for line in lines:
if line[:2] == '</': indent = indent - 1
newlines.append(indent * ' ' + line)
if not line[:2] == '</' and line[-1:] == '>' and \
not line[-2:] in ['/>', '->']: indent = indent + 1
text = '\n'.join(newlines)
text = re.compile(
'\<div(?P<a>( .+)?)\>\s+\</div\>').sub('<div\g<a>></div>', text)
text = re.compile('\<a(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</a\>').sub('<a\g<a>>\g<b></a>', text)
text = re.compile('\<b(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</b\>').sub('<b\g<a>>\g<b></b>', text)
text = re.compile('\<i(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</i\>').sub('<i\g<a>>\g<b></i>', text)
text = re.compile('\<span(?P<a>( .+)?)\>\s+(?P<b>[\w\s\(\)\/]+?)\s+\</span\>').sub('<span\g<a>>\g<b></span>', text)
text = re.compile('\s+\<br(?P<a>.*?)\/\>').sub('<br\g<a>/>', text)
text = re.compile('\>(?P<a>\s+)(?P<b>[\.\,\:\;])').sub('>\g<b>\g<a>', text)
text = re.compile('\n\s*\n').sub('\n', text)
for script in scripts:
text = text.replace('<script />', script, 1)
for style in styles:
text = text.replace('<style />', cleancss(style), 1)
return text
def read_file(filename):
f = open(filename, 'r')
try:
return f.read()
finally:
f.close()
for file in sys.argv[1:]:
data = read_file(file)
open(file+'.bak2', 'w').write(data)
if file[-4:] == '.css':
data = cleancss(data)
if file[-5:] == '.html':
data = cleanhtml(data)
open(file, 'w').write(data)
-17
View File
@@ -1,17 +0,0 @@
import re
def cleanjs(text):
text = re.sub('\s*}\s*', '\n}\n', text)
text = re.sub('\s*{\s*', ' {\n', text)
text = re.sub('\s*;\s*', ';\n', text)
text = re.sub('\s*,\s*', ', ', text)
text = re.sub('\s*(?P<a>[\+\-\*/\=]+)\s*', ' \g<a> ', text)
lines = text.split('\n')
text = ''
indent = 0
for line in lines:
rline = line.strip()
if rline:
pass
return text

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