Compare commits

...

228 Commits

Author SHA1 Message Date
mdipierro f93191f406 new admin 2016-03-09 11:55:36 -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
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
mdipierro 71fba07e3a some sqlhtml renaming 2015-12-27 07:12:56 -06:00
mdipierro 2a7a4a3d04 removed print statements 2015-12-27 05:39:08 -06:00
mdipierro 999f235b75 IS_IN_DB(...,delimiter=',',auto_add=True) 2015-12-27 05:33:29 -06:00
mdipierro da22554aed allow for IS_IN_DB(db,db.thing.id,db.thing.name) 2015-12-27 02:46:23 -06:00
mdipierro 0409d6f725 R-2.13.4 2015-12-25 22:56:50 -06:00
mdipierro b6235249da fixed path issue when starting web2py 2015-12-25 22:56:13 -06:00
mdipierro fabadcd21f do not remove gluon/packages/dal/.* files 2015-12-24 09:40:08 -06:00
mdipierro 8e4ea3497b fixed issue #1138, ldap and python 2.6.x problem 2015-12-24 09:15:19 -06:00
mdipierro 4b0e1856b5 reverted 2a245d36 2015-12-24 09:04:45 -06:00
mdipierro 94841c90c3 R-2.13.3 2015-12-24 08:50:24 -06:00
mdipierro f14e5f728c fixing mess 2015-12-24 08:48:19 -06:00
mdipierro 8443c17839 sync'ed appadmins 2015-12-24 08:36:16 -06:00
mdipierro be8114127e Merge pull request #1141 from gi0baro/master
Tracking latest pyDAL changes
2015-12-24 08:30:31 -06:00
gi0baro 4eaef303ff Tracking latest pyDAL changes 2015-12-24 15:21:52 +01:00
mdipierro 319a3fc1dc Merge branch 'BuhtigithuB-fix/no-more-deprecated-has-key' 2015-12-23 23:11:55 -06:00
mdipierro 463d643e2c fmerged 2015-12-23 23:11:34 -06:00
mdipierro 0cbed12952 Merge pull request #1137 from cassiobotaro/fix_logging
fixing logging old behaviour
2015-12-23 23:08:17 -06:00
mdipierro b5994e57a4 Merge pull request #1136 from cassiobotaro/remove_25_support
update files removing 2.5 things
2015-12-23 23:07:54 -06:00
Cássio Botaro e239b975be Change to re-run AppVeyor 2015-12-23 11:19:19 -02:00
Richard Vézina 0259ea3d29 no more deprecated .has_key(...) 2015-12-22 15:39:32 -05:00
cassiobotaro db4c008de3 Minor changes 2015-12-21 15:40:25 -02:00
cassiobotaro 7921e5148a fixing logging old behaviour 2015-12-21 12:10:50 -02:00
cassiobotaro ee23eab77a update files removing 2.5 things 2015-12-21 11:55:44 -02:00
mdipierro 2344386f77 better docstring for Auth.jwt 2015-12-18 19:19:43 -06:00
mdipierro b5e12031c5 added Auth(db,jwt=dict(secret_key='secret')) and auth.allows_jwt() before auth.requires_login() 2015-12-18 19:12:41 -06:00
mdipierro 85e6840cf0 Merge branch 'master' of github.com:web2py/web2py 2015-12-18 18:09:09 -06:00
mdipierro 4c3006acb4 Merge pull request #1135 from gi0baro/master
Track latest pyDAL GAE fix by @mdipierro
2015-12-18 18:08:53 -06:00
gi0baro f8f008cab5 Track latest pyDAL GAE fix by @mdipierro 2015-12-18 12:51:17 +01:00
mdipierro 6bff8af458 CHANGELOG 2015-12-18 04:52:43 -06:00
mdipierro b67edb083e fixed compileapp problem in appveyor test (2nd attempt) 2015-12-18 04:44:03 -06:00
mdipierro 4125a97ce1 fixed compileapp problem in appveyor test 2015-12-18 04:39:51 -06:00
mdipierro 78cf55bf9a R-2.13.2 2015-12-18 04:28:16 -06:00
mdipierro 931daaff89 fixed security issue in reset password when registration_requires_authorization, thanks Giovanni Verde 2015-12-18 04:11:26 -06:00
mdipierro c6550f0adc fixed a condition that allows reset_password if a reset link is sent before a user is blocked 2015-12-18 03:40:12 -06:00
mdipierro 22c89d8dcc version 2.13.1 2015-12-17 21:19:08 -06:00
mdipierro 1636528a0f Merge pull request #1132 from gi0baro/master
Tracking pyDAL v15.12
2015-12-17 21:08:30 -06:00
gi0baro c02229d79c Tracking pyDAL v15.12 2015-12-16 21:55:23 +01:00
mdipierro acb05dbfe1 added retrieve to fabfile and fixed minor bug 2015-12-15 23:13:46 -06:00
mdipierro fda1117dd7 fixed import 2015-12-14 15:44:34 -06:00
mdipierro d1fde23182 create_user spelling fixed 2015-12-14 15:43:50 -06:00
mdipierro adf4c93860 Merge pull request #1124 from matclab/issue1123
Convert attachments to a list if necessary.
2015-12-14 15:32:07 -06:00
mdipierro 02f1903c3d Merge pull request #1127 from gi0baro/appveyor-skip
Avoid to run pyDAL adapters tests on appveyor
2015-12-14 15:31:23 -06:00
mdipierro 1137027ecc Merge pull request #1126 from nextghost/master
Parse GET vars from rewritten query string, not the original. Fixes #730
2015-12-14 15:31:10 -06:00
mdipierro 229616b9fc added fabfile form deployment and maintenance tasks 2015-12-14 15:16:00 -06:00
gi0baro a75a8cbf46 Avoid to run pyDAL adapters tests on appveyor 2015-12-13 12:34:54 +01:00
Martin Doucha 9650ff7516 Parse GET vars from rewritten query string, not the original. Fixes #730 2015-12-12 23:59:15 +01:00
Mathieu Clabaut 5b90f3f532 Convert attachments to a list if necessary.
Also corrects a typo that was apparently silenced by the bug.
This closes issue #1123
2015-12-09 14:46:05 +01:00
mdipierro 483092787b Merge pull request #1120 from dokime7/patch-10
Fix oauth2 renew token
2015-12-07 19:58:24 -06:00
Jeremie Dokime 9b17048882 Fix oauth2 renew token
When getting an oauth token, we must use code or refresh_token but not twice.
2015-12-07 11:18:59 +01:00
mdipierro 30fe7400f9 syncing latest mydal 2015-12-04 15:14:02 -06:00
mdipierro ada9353a7e removed unwanted referene to jwt in tools 2015-12-04 15:10:25 -06:00
mdipierro b0373297e0 Browser auto-fill can corrupt data while using admin database interface #1045, thanks dkleissa 2015-12-04 12:31:35 -06:00
mdipierro 2dbbef724c DIV.elements fails when searching for an attribute with an integer value #1074 2015-12-04 12:21:05 -06:00
mdipierro eb7017fd9a fixed auth.settings.register_onaccept is not firing when signing up through third-party #1081 2015-12-04 12:14:39 -06:00
mdipierro 4c039574df skip failed views, thanks Anthony 2015-12-04 12:06:46 -06:00
mdipierro ab900957fe fixed Cookie parsing stopped after first invalid cookie #1084, thanks paultuckey 2015-12-04 11:44:51 -06:00
mdipierro f960c8f6df fixed add_membership, del_membership, add_membership = IntegrityError (when auth.enable_record_versioning) #1087 2015-12-04 11:42:06 -06:00
mdipierro d2910327c0 temp fix for IS_DATETIME with timezone info & record versioning causes TypeError: can't compare offset-naive and offset-aware datetimes #1094 2015-12-04 11:31:55 -06:00
mdipierro dfd6d52192 fixed missing helper.get(attribute) 2015-12-04 11:28:39 -06:00
mdipierro 7dd8a3c853 fixed Reference to http://..../[app]/default/user/manage_users in welcome's default controller #1102 2015-12-04 11:24:00 -06:00
mdipierro 71ae754fcd fixed #1110, allow passing unicode to template render 2015-12-04 11:16:15 -06:00
mdipierro 0520770a7e fixed #1111, Validator IS_IPV4: options is_private=False not working, thanks Nbushkov 2015-12-04 11:12:44 -06:00
mdipierro 6b880fb455 fixed class concatenation 2015-12-04 10:46:20 -06:00
mdipierro dd180019a1 Merge pull request #1118 from matclab/readonly-label
Add readonly class to labels of readonly fields and set padding to 0
2015-12-04 10:43:17 -06:00
mdipierro 638f1f902a Merge pull request #1116 from timnyborg/patch-7
Fix IS_NOT_IN_DB to work with custom primarykey
2015-12-04 10:42:25 -06:00
mdipierro 73061e3bf5 Merge pull request #1114 from BuhtigithuB/Improve/ldap-contrib-self-signed-certificate
Improve/ldap contrib self signed certificate
2015-12-04 10:42:05 -06:00
mdipierro 5a18e29c2e Merge pull request #1113 from timnyborg/patch-6
Fix grid count when using groupby on MSSQL
2015-12-04 10:41:36 -06:00
mdipierro 721af77c90 Merge pull request #1108 from dokime7/patch-9
Send client_secret when get oauth2 refresh_token
2015-12-04 10:38:04 -06:00
mdipierro 2cf6797b43 Merge pull request #1106 from erilyth/master
Added XML to retrieve pickled XML data #1067
2015-12-04 10:37:50 -06:00
mdipierro 5c4145743f Merge pull request #1103 from ilvalle/w2p_target
added w2p_target attribute in form to control the output destination
2015-12-04 10:37:14 -06:00
mdipierro b4733e4617 added gluon/contrib/web2py_jwt.py, thanks Niphlod 2015-12-04 10:30:56 -06:00
mdipierro b51d217d9b french pluralizaiton rules, thanks Mathieu Clabaut 2015-12-04 10:07:38 -06:00
Mathieu Clabaut 864dbe73f2 Add readonly class to labels of readonly fields and set padding to 0
This allow for readonly fields content to be vertical aligned with their label
2015-12-04 15:29:45 +01:00
Tim Nyborg b942fc8f7a Fix IS_NOT_IN_DB to work with custom primarykey
Validator currently selects custom id properly, but then explicitly checks .id
2015-12-03 15:02:21 +00:00
Hardirc b2a65dbba4 Support for self-signed certificate LDAPS implementation 2015-12-02 14:18:09 -05:00
Hardirc d36d4d77f7 replace .has_key() by in, .has_key() is deprecated 2015-12-02 14:04:51 -05:00
Hardirc c8db6d5fb7 Improve PEP8 and readability 2015-12-02 14:02:27 -05:00
Tim Nyborg 1b77c2294a Fix grid count when using groupby on MSSQL
Any query like SELECT COUNT(*) from (SELECT COUNT(*) FROM ...) will fail with 'No column name was specified for column 1 of '_tmp'', so I'm providing an alias for the subquery's field
2015-11-30 16:09:53 +00:00
Jeremie Dokime 98a81c9fbd Send client_secret when get oauth2 refresh_token
With some oauth2 providers, the oauth client_secret is required when getting a refresh_token.
2015-11-23 10:10:00 +01:00
Batchu Venkat Vishal 4bf5a70dc0 Added XML to retrieve pickled XML data #1067 2015-11-19 15:01:23 +05:30
ilvalle d883e3d84e added w2p_target attribute in form to control the output destination 2015-11-14 12:03:20 +01:00
mdipierro db37cf6a58 Revert "Labels should get their information from the render function of the records."
This reverts commit 65c87386c1.
2015-11-12 18:25:48 -06:00
mdipierro dba5c97d51 fixed check for form tampering 2015-11-11 18:38:48 -06:00
mdipierro 948bd0c671 fixed check for form tampering 2015-11-11 18:20:37 -06:00
mdipierro 5d8ff8ba2c removed login_once_after_registration 2015-11-11 09:14:05 -06:00
mdipierro 503cd59adc auth.settings.login_once_after_registration 2015-11-11 09:03:54 -06:00
mdipierro a0bcd2287b Merge branch 'josedesoto-issue/1095' 2015-10-30 23:10:35 -05:00
mdipierro 430163f70b fixed conflict 2015-10-30 23:10:25 -05:00
mdipierro 52b59e9b71 Merge pull request #1092 from niphlod/fix/1090
fixes #1090
2015-10-30 23:08:47 -05:00
mdipierro e8f87ea274 Merge pull request #1091 from dokime7/patch-8
Add renew token by using refresh_token
2015-10-30 23:08:37 -05:00
mdipierro fb6fa0c448 Merge pull request #1088 from niphlod/fix/1083
fixes #1083
2015-10-30 23:07:58 -05:00
mdipierro 935c95ccfc Merge pull request #1086 from gi0baro/pydal-tests
Run pydal's tests inside web2py using contrib adapters
2015-10-30 23:07:37 -05:00
mdipierro e180e69467 fixed a typo, thanks James Burke 2015-10-30 23:06:00 -05:00
engeens 5c9d197f93 issue #1095. Added two-factor authentication methods and onvalidation. Fixed last attempt two-factor retry login
issue #1095. Added return user for two_factor_onvalidation
2015-10-30 15:09:51 +01:00
mdipierro e417d311e5 added link to http://www.web2pyref.com/ 2015-10-29 20:59:15 -05:00
mdipierro 199f93f262 fixed typo in tools.py, thanks James Burke 2015-10-29 20:56:40 -05:00
niphlod 64a8880c80 fixes #1090
removed timezone for IS_DATE* validators
2015-10-26 09:50:09 +01:00
Jeremie Dokime 257c514bd4 Add renew token by using refresh_token
Handle the refresh_token mechanism to renew the access_token when expire.
2015-10-25 20:48:04 +01:00
niphlod 12f848c899 fixes #1083 2015-10-19 21:50:34 +02:00
mdipierro 4de007a946 fixed possible problem with cache.action 2015-10-16 21:39:30 -05:00
gi0baro b59a93e24e Run pydal's tests inside web2py using contrib adapters 2015-10-16 14:44:41 +02:00
mdipierro bbed326c20 fixed commit error 2015-10-07 13:15:39 -05:00
mdipierro 874398c38c Merge pull request #1080 from BuhtigithuB/improve/ldap-auth-more-dry
Make ldap_auth a bit more DRY
2015-10-07 13:12:30 -05:00
mdipierro a9f8fbadae Merge pull request #1077 from leonelcamara/fix_wiki_extra
Fixes #721
2015-10-07 13:08:16 -05:00
mdipierro e62320ff9f Merge pull request #1076 from gi0baro/master
Tracking pydal 15.09
2015-10-07 13:07:54 -05:00
mdipierro b3e606295e committed changelog and removed unwanted file 2015-10-07 13:04:12 -05:00
mdipierro b8c2bd7303 fixed LazyCrypt and fixed git problem 2015-10-07 13:02:30 -05:00
mdipierro 1387b26606 fixed LazyCrypt, thanks Denes 2015-10-07 12:57:20 -05:00
Richard Vézina c6a7732d32 Don't update record when values are the same 2015-10-06 14:36:45 -04:00
Richard Vézina 0036d9c45b Make ldap_auth a bit more DRY 2015-10-06 14:30:50 -04:00
Leonel Câmara b99fb7dedf Fixes #721
Fixes a bug where auth.wiki was not respecting the extra keyword argument
2015-09-29 00:21:01 +01:00
gi0baro 344590470b Tracking pydal 15.09 2015-09-28 15:50:42 +02:00
mdipierro 2c57dc084e Merge pull request #1073 from niphlod/fix/1043
fixes #1043 , thanks @bobstjon
2015-09-27 14:40:27 -05:00
mdipierro c17ba0a020 Merge pull request #1072 from niphlod/fix/1039
fixes #1039
2015-09-27 14:40:14 -05:00
mdipierro 7d4b460e1b Merge pull request #1071 from niphlod/fix/1068
fixes #1068
2015-09-27 14:39:58 -05:00
mdipierro 6680ea8ab7 Merge pull request #1061 from nklever/master
small changes in sqlhtml.py, validator.py, contrib/spreadsheet.py
2015-09-27 14:39:47 -05:00
niphlod 353db90a64 fixes #1043 , thanks @bobstjon 2015-09-21 22:24:59 +02:00
niphlod 827e663ac4 better list: widget 2015-09-21 22:18:18 +02:00
niphlod de399691ce fixes #1039
It was a REEEAALLY good catch :-)
2015-09-21 22:09:17 +02:00
niphlod 46f081c45c fixes #1068
threw in also a better list:string widget repr for bs3

Updated also the bootswatch theme because something was wrong
2015-09-21 21:38:28 +02:00
mdipierro 0fa0dbaeea Merge branch 'master' of github.com:web2py/web2py 2015-09-20 14:07:06 -05:00
mdipierro b47511c896 token default = web2py_uuid 2015-09-20 14:07:01 -05:00
mdipierro e31318eaa8 Merge pull request #1066 from lraphael/master
fix unindented lines
2015-09-18 00:41:34 -05:00
mdipierro 72ee538883 Merge pull request #1065 from leonelcamara/pa_integration
PythonAnywhere integration
2015-09-18 00:41:20 -05:00
mdipierro b6ddc6098e Merge pull request #1060 from viniciusban/1059-response-render-uses-original-response-view
Closes #1059: get `response.view` from the environment
2015-09-18 00:40:04 -05:00
mdipierro 90854eae44 Merge pull request #1058 from niphlod/fix/735
fixes issue #735
2015-09-18 00:39:08 -05:00
mdipierro 2bceb3f95f Merge pull request #1057 from niphlod/fix/823
fixes #823
2015-09-18 00:38:55 -05:00
mdipierro 9da1e29014 Merge pull request #1056 from niphlod/fix/wiki_typo
fixes typo in wiki.
2015-09-18 00:38:38 -05:00
Raphael Lechner 39ba9dc1a9 fix unindent lines 2015-09-16 17:12:45 +02:00
Leonel Câmara 36db9719ef Deal with the corner case of already created accounts
Polished everything a bit
2015-09-15 17:24:57 +01:00
Leonel Câmara 125cbd93a0 Allow deploying to pythonanywhere from the web2py admin that you're running locally. 2015-09-08 00:51:09 +01:00
Nik Klever bc267ce17b Added Column- and Row-Headers to be more flexible with the headers of the
spreadsheet.

Added also a boolean "select" parameter for the sheet.cell function which
allows to use a HTML select tag instead of an input tag for this cell.
2015-09-05 15:01:02 +02:00
Nik Klever 65c87386c1 Labels should get their information from the render function of the records. 2015-09-05 14:57:55 +02:00
Nik Klever 2a245d36f4 If in any of the form fields are unicode strings entered as input, the
unicode characters in these strings are lost in self.vars.

This conditions sets it back to the original input.

Might be, that this should be done at another place, but it works.
2015-09-05 14:38:32 +02:00
viniciusban dcf64a661d Closes #1059: get response.view from the environment 2015-09-03 20:39:37 -03:00
niphlod 1c74afc01b fixes issue #735 2015-09-03 18:33:54 +02:00
niphlod 5dbcda9f38 fixes #823 2015-09-03 18:08:33 +02:00
niphlod ac02d52f05 fixes typo in wiki. As usual, lack of unittests made this possible.
We should really make each developer "adopt" a piece of web2py to test
and care if we don't want to write unittests.
2015-09-03 17:56:45 +02:00
mdipierro d4270373e1 fixed bug in redirect to cas service, thanks Fernando González 2015-09-01 23:07:18 -05:00
mdipierro e4b27080ca Merge pull request #1051 from ShySec/master
added HttpOnly cookies (default)
2015-08-30 20:41:23 -05:00
mdipierro 692791a518 Merge pull request #1053 from BuhtigithuB/feature/redirect-next-var-when-logged-on-page-reload
No credentials request if logged in and URL contains user/login?_next=
2015-08-30 00:58:27 -05:00
mdipierro 9190191c7a changed the default BS3 theme 2015-08-23 10:07:32 -05:00
mdipierro 7bd8f6a1a9 Merge pull request #1054 from BuhtigithuB/Improve/PEP8-gluon-tools-py
Improve PEP8 gluon/tools.py
2015-08-21 00:07:06 -05:00
mdipierro 64e115f442 Merge pull request #1050 from timnyborg/patch-5
Update simplejsonrpc.py - move default to method signature
2015-08-21 00:03:24 -05:00
Richard Vézina 61f685d225 Improve PEP8 gluon/tools.py 2015-08-20 17:16:13 -04:00
Richard Vézina c56fc2f6a0 Improve proposed enhancement #1052 2015-08-20 15:23:59 -04:00
mdipierro bb2aa29867 fixed google link to viewer in autolinks.py 2015-08-20 14:00:21 -05:00
Richard Vézina 08b6832809 No credentials request if logged in and URL contains user/login?_next= 2015-08-19 14:47:21 -04:00
kelson cbbd1246db re import required for assertRegexpMatches port 2015-08-19 11:33:47 -04:00
kelson 0a79bf3afd assertRegexpMatches requires port for python2.5 and python2.6 2015-08-19 11:30:07 -04:00
kelson db5e58e49f added HttpOnly cookies (default)
added unit tests for cookie layout, secure cookies, and HttpOnly cookies
Session.httponly_cookies=False to revert HttpOnly cookies
2015-08-19 11:25:00 -04:00
Tim Nyborg 5030d3144f Update simplejsonrpc.py
Default best placed in method signature, 
Thanks to cassiobotaro for pointing it out
2015-08-19 11:59:40 +01:00
128 changed files with 7355 additions and 6551 deletions
+1
View File
@@ -59,3 +59,4 @@ HOWTO-web2py-devel
*.sublime-workspace *.sublime-workspace
.idea/* .idea/*
site-packages/ site-packages/
logs/
+7
View File
@@ -18,6 +18,10 @@ before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install --download-cache $HOME/.pip-cache unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi; - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --download-cache $HOME/.pip-cache codecov; fi
- mysql -e 'create database pydal;'
- psql -c 'create database pydal;' -U postgres
- psql -c 'create extension postgis;' -U postgres -d pydal;
- psql -c 'SHOW SERVER_VERSION' -U postgres
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
@@ -28,3 +32,6 @@ after_success:
notifications: notifications:
email: true email: true
addons:
postgresql: "9.4"
+45 -1
View File
@@ -1,4 +1,48 @@
## 2.12.1 ## trunk
- new JWT implementation (experimental)
- new gluon.contrib.redis_scheduler
- BREAKING: changes to gluon.contrib.redis_cache
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)
- BREAKING: changes to gluon.contrib.redis_session
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.1-2
- 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)
- allow passing unicode to template render
- allow IS_NOT_IN_DB to work with custom primarykey, thanks timmyborg
- allow HttpOnly cookies
- french pluralizaiton rules, thanks Mathieu Clabaut
- fixed bug in redirect to cas service, thanks Fernando González
- allow deploying to pythonanywhere from the web2py admin that you're running locally, thanks Leonel
- better tests
- many more bug fixes
## 2.12.1-3
- security fix: Validate for open redirect everywhere, not just in login() - security fix: Validate for open redirect everywhere, not just in login()
- allow to pack invidual apps and selected files as packed exe files - allow to pack invidual apps and selected files as packed exe files
+2 -2
View File
@@ -11,7 +11,7 @@ clean:
find ./ -name '*.rej' -exec rm -f {} \; find ./ -name '*.rej' -exec rm -f {} \;
find ./ -name '#*' -exec rm -f {} \; find ./ -name '#*' -exec rm -f {} \;
find ./ -name 'Thumbs.db' -exec rm -f {} \; find ./ -name 'Thumbs.db' -exec rm -f {} \;
find ./gluon/ -name '.*' -exec rm -f {} \; # find ./gluon/ -name '.*' -exec rm -f {} \;
find ./gluon/ -name '*class' -exec rm -f {} \; find ./gluon/ -name '*class' -exec rm -f {} \;
find ./applications/admin/ -name '.*' -exec rm -f {} \; find ./applications/admin/ -name '.*' -exec rm -f {} \;
find ./applications/examples/ -name '.*' -exec rm -f {} \; find ./applications/examples/ -name '.*' -exec rm -f {} \;
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked" echo "remember that pymysql was tweaked"
src: src:
### Use semantic versioning ### Use semantic versioning
echo 'Version 2.12.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION echo 'Version 2.13.4-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files ### rm -f all junk files
make clean make clean
### clean up baisc apps ### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.12.3-stable+timestamp.2015.08.18.19.14.07 Version 2.13.4-stable+timestamp.2016.02.10.15.41.11
+1 -1
View File
@@ -576,7 +576,7 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC') meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
+7 -1
View File
@@ -484,9 +484,15 @@ def cleanup():
def compile_app(): def compile_app():
app = get_app() app = get_app()
c = app_compile(app, request) c = app_compile(app, request,
skip_failed_views = (request.args(1) == 'skip_failed_views'))
if not c: if not c:
session.flash = T('application compiled') session.flash = T('application compiled')
elif isinstance(c, list):
session.flash = DIV(*[T('application compiled'), BR(), BR(),
T('WARNING: The following views could not be compiled:'), BR()] +
[CAT(BR(), view) for view in c] +
[BR(), BR(), T('DO NOT use the "Pack compiled" feature.')])
else: else:
session.flash = DIV(T('Cannot compile: there are errors in your app:'), session.flash = DIV(T('Cannot compile: there are errors in your app:'),
CODE(c)) CODE(c))
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
import base64
import os
import re
import gzip
import tarfile
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from xmlrpclib import ProtocolError
from gluon.contrib.simplejsonrpc import ServerProxy
def deploy():
response.title = T('Deploy to pythonanywhere')
return {}
def create_account():
""" Create a PythonAnywhere account """
if not request.vars:
raise HTTP(400)
if request.vars.username and request.vars.web2py_admin_password:
# Check if web2py is already there otherwise we get an error 500 too.
client = ServerProxy('https://%(username)s:%(web2py_admin_password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
try:
if client.login() is True:
return response.json({'status': 'ok'})
except ProtocolError as error:
pass
import urllib, urllib2
url = 'https://www.pythonanywhere.com/api/web2py/create_account'
data = urllib.urlencode(request.vars)
req = urllib2.Request(url, data)
try:
reply = urllib2.urlopen(req)
except urllib2.HTTPError as error:
if error.code == 400:
reply = error
elif error.code == 500:
return response.json({'status':'error', 'errors':{'username': ['An App other than web2py is installed in the domain %(username)s.pythonanywhere.com' % request.vars]}})
else:
raise
response.headers['Content-Type'] = 'application/json'
return reply.read()
def list_apps():
""" Get a list of apps both remote and local """
if not request.vars.username or not request.vars.password:
raise HTTP(400)
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
regex = re.compile('^\w+$')
local = [f for f in os.listdir(apath(r=request)) if regex.match(f)]
try:
pythonanywhere = client.list_apps()
except ProtocolError as error:
raise HTTP(error.errcode)
return response.json({'local': local, 'pythonanywhere': pythonanywhere})
def bulk_install():
""" Install a list of apps """
def b64pack(app):
"""
Given an app's name, return the base64 representation of its packed version.
"""
folder = apath(app, r=request)
tmpfile = StringIO()
tar = tarfile.TarFile(fileobj=tmpfile, mode='w')
try:
filenames = listdir(folder, '^[\w\.\-]+$', add_dirs=True,
exclude_content_from=['cache', 'sessions', 'errors'])
for fname in filenames:
tar.add(os.path.join(folder, fname), fname, False)
finally:
tar.close()
tmpfile.seek(0)
gzfile = StringIO()
w2pfp = gzip.GzipFile(fileobj=gzfile, mode='wb')
w2pfp.write(tmpfile.read())
w2pfp.close()
gzfile.seek(0)
return base64.b64encode(gzfile.read())
request.vars.apps = request.vars['apps[]']
if not request.vars.apps or not request.vars.username or not request.vars.password:
raise HTTP(400)
if not isinstance(request.vars.apps, list):
request.vars.apps = [request.vars.apps] # Only one app selected
client = ServerProxy('https://%(username)s:%(password)s@%(username)s.pythonanywhere.com/admin/webservices/call/jsonrpc' % request.vars)
for app in request.vars.apps:
try:
client.install(app, app+'.w2p', b64pack(app))
except ProtocolError as error:
raise HTTP(error.errcode)
return response.json({'status': 'ok'})
+408 -377
View File
@@ -1,377 +1,408 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'!langcode!': 'pt', '!langcode!': 'pt',
'!langname!': 'Português', '!langname!': 'Português',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN', '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" é uma expressão opcional como "campo1=\'novo_valor\'". Não é permitido atualizar ou apagar resultados de um JOIN',
'%s %%{row} deleted': '%s registros apagados', '%s %%{row} deleted': '%s registros apagados',
'%s %%{row} updated': '%s registros atualizados', '%s %%{row} updated': '%s registros atualizados',
'%Y-%m-%d': '%d/%m/%Y', '%Y-%m-%d': '%d/%m/%Y',
'%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S', '%Y-%m-%d %H:%M:%S': '%d/%m/%Y %H:%M:%S',
'(requires internet access)': '(requer acesso à internet)', '(requires internet access)': '(requer acesso à internet)',
'(requires internet access, experimental)': '(requer acesso à internet, experimental)', '(requires internet access, experimental)': '(requer acesso à internet, experimental)',
'(something like "it-it")': '(algo como "it-it")', '(something like "it-it")': '(algo como "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)', '@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(file **gluon/contrib/plural_rules/%s.py** is not found)',
'@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page', '@markmin\x01An error occured, please [[reload %s]] the page': 'An error occured, please [[reload %s]] the page',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files', '@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Está disponível uma nova versão do web2py', 'A new version of web2py is available': 'Está disponível uma nova versão do web2py',
'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s', 'A new version of web2py is available: %s': 'Está disponível uma nova versão do web2py: %s',
'About': 'sobre', 'About': 'sobre',
'About application': 'Sobre a aplicação', 'About application': 'Sobre a aplicação',
'additional code for your application': 'código adicional para sua aplicação', 'Accept Terms': 'Accept Terms',
'Additional code for your application': 'Código adicional para a sua aplicação', 'additional code for your application': 'código adicional para sua aplicação',
'admin disabled because no admin password': ' admin desabilitado por falta de senha definida', 'Additional code for your application': 'Código adicional para a sua aplicação',
'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE', 'admin disabled because no admin password': ' admin desabilitado por falta de senha definida',
'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha', 'admin disabled because not supported on google app engine': 'admin dehabilitado, não é soportado no GAE',
'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro', 'admin disabled because unable to access password file': 'admin desabilitado, não foi possível ler o arquivo de senha',
'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro', 'Admin is disabled because insecure channel': 'Admin desabilitado pois o canal não é seguro',
'Admin language': 'Linguagem do Admin', 'Admin is disabled because unsecure channel': 'Admin desabilitado pois o canal não é seguro',
'administrative interface': 'interface administrativa', 'Admin language': 'Linguagem do Admin',
'Administrator Password:': 'Senha de administrador:', 'administrative interface': 'interface administrativa',
'and rename it (required):': 'e renomeie (requerido):', 'Administrator Password:': 'Senha de administrador:',
'and rename it:': ' e renomeie:', 'and rename it (required):': 'e renomeie (requerido):',
'appadmin': 'appadmin', 'and rename it:': ' e renomeie:',
'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro', 'appadmin': 'appadmin',
'application "%s" uninstalled': 'aplicação "%s" desinstalada', 'appadmin is disabled because insecure channel': 'admin desabilitado, canal inseguro',
'application compiled': 'aplicação compilada', 'application "%s" uninstalled': 'aplicação "%s" desinstalada',
'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada', 'application compiled': 'aplicação compilada',
'Application name:': 'Nome da aplicação:', 'application is compiled and cannot be designed': 'A aplicação está compilada e não pode ser modificada',
'are not used': 'não usadas', 'Application name:': 'Nome da aplicação:',
'are not used yet': 'ainda não usadas', 'are not used': 'não usadas',
'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?', 'are not used yet': 'ainda não usadas',
'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?', 'Are you sure you want to delete file "%s"?': 'Tem certeza que deseja apagar o arquivo "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?', 'Are you sure you want to delete plugin "%s"?': 'Tem certeza que deseja apagar o plugin "%s"?',
'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?', 'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?', 'Are you sure you want to uninstall application "%s"': 'Tem certeza que deseja apagar a aplicação "%s"?',
'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?', 'Are you sure you want to uninstall application "%s"?': 'Tem certeza que deseja apagar a aplicação "%s"?',
'arguments': 'argumentos', 'Are you sure you want to upgrade web2py now?': 'Tem certeza que deseja atualizar o web2py agora?',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.', 'arguments': 'argumentos',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.', 'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATENÇÃO o login requer uma conexão segura (HTTPS) ou executar de localhost.',
'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!', 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATENÇÃO OS TESTES NÃO THREAD SAFE, NÃO EFETUE MÚLTIPLOS TESTES AO MESMO TEMPO.',
'Autocomplete Python Code': 'Autocompletar Código Python', 'ATTENTION: you cannot edit the running application!': 'ATENÇÃO: Não pode modificar a aplicação em execução!',
'Available databases and tables': 'Bancos de dados e tabelas disponíveis', 'Autocomplete Python Code': 'Autocompletar Código Python',
'back': 'voltar', 'Available databases and tables': 'Bancos de dados e tabelas disponíveis',
'browse': 'buscar', 'back': 'voltar',
'cache': 'cache', 'Begin': 'Begin',
'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas', 'browse': 'buscar',
'can be a git repo': 'can be a git repo', 'cache': 'cache',
'Cannot be empty': 'Não pode ser vazio', 'cache, errors and sessions cleaned': 'cache, erros e sessões eliminadas',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente', 'can be a git repo': 'can be a git repo',
'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação', 'Cannot be empty': 'Não pode ser vazio',
'cannot create file': 'Não é possível criar o arquivo', 'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Não é possível compilar: Existem erros em sua aplicação. Depure, corrija os errros e tente novamente',
'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"', 'Cannot compile: there are errors in your app:': 'Não é possível compilar: Existem erros em sua aplicação',
'Change admin password': 'mudar senha de administrador', 'cannot create file': 'Não é possível criar o arquivo',
'change editor settings': 'mudar definições do editor', 'cannot upload file "%(filename)s"': 'não é possível fazer upload do arquivo "%(filename)s"',
'Change Password': 'Trocar Senha', 'Change admin password': 'mudar senha de administrador',
'check all': 'marcar todos', 'change editor settings': 'mudar definições do editor',
'Check for upgrades': 'checar por atualizações', 'Change Password': 'Trocar Senha',
'Check to delete': 'Marque para apagar', 'check all': 'marcar todos',
'Checking for upgrades...': 'Buscando atualizações...', 'Check for upgrades': 'checar por atualizações',
'Clean': 'limpar', 'Check to delete': 'Marque para apagar',
'click here for online examples': 'clique para ver exemplos online', 'Checking for upgrades...': 'Buscando atualizações...',
'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa', 'Clean': 'limpar',
'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro', 'click here for online examples': 'clique para ver exemplos online',
'click to check for upgrades': 'clique aqui para checar por atualizações', 'click here for the administrative interface': 'Clique aqui para acessar a interface administrativa',
'click to open': 'clique para abrir', 'Click row to expand traceback': 'Clique em uma coluna para expandir o log do erro',
'Client IP': 'IP do cliente', 'click to check for upgrades': 'clique aqui para checar por atualizações',
'code': 'código', 'click to open': 'clique para abrir',
'collapse/expand all': 'colapsar/expandir tudo', 'Client IP': 'IP do cliente',
'commit (mercurial)': 'commit (mercurial)', 'code': 'código',
'Compile': 'compilar', 'collapse/expand all': 'colapsar/expandir tudo',
'compiled application removed': 'aplicação compilada removida', 'commit (mercurial)': 'commit (mercurial)',
'Controllers': 'Controladores', 'Compile': 'compilar',
'controllers': 'controladores', 'compiled application removed': 'aplicação compilada removida',
'Count': 'Contagem', 'Controllers': 'Controladores',
'Create': 'criar', 'controllers': 'controladores',
'create file with filename:': 'criar um arquivo com o nome:', 'Count': 'Contagem',
'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente', 'Create': 'criar',
'create new application:': 'nome da nova aplicação:', 'create file with filename:': 'criar um arquivo com o nome:',
'Create new simple application': 'Crie uma nova aplicação', 'Create new application using the Wizard': 'Criar nova aplicação utilizando o assistente',
'Create/Upload': 'Create/Upload', 'create new application:': 'nome da nova aplicação:',
'created by': 'criado por', 'Create new simple application': 'Crie uma nova aplicação',
'crontab': 'crontab', 'Create/Upload': 'Create/Upload',
'Current request': 'Requisição atual', 'created by': 'criado por',
'Current response': 'Resposta atual', 'crontab': 'crontab',
'Current session': 'Sessão atual', 'Current request': 'Requisição atual',
'currently running': 'Executando', 'Current response': 'Resposta atual',
'currently saved or': 'Atualmente salvo ou', 'Current session': 'Sessão atual',
'customize me!': 'Modifique-me', 'currently running': 'Executando',
'data uploaded': 'Dados enviados', 'currently saved or': 'Atualmente salvo ou',
'database': 'banco de dados', 'customize me!': 'Modifique-me',
'database %s select': 'Seleção no banco de dados %s', 'data uploaded': 'Dados enviados',
'database administration': 'administração de banco de dados', 'database': 'banco de dados',
'Date and Time': 'Data e Hora', 'database %s select': 'Seleção no banco de dados %s',
'db': 'db', 'database administration': 'administração de banco de dados',
'Debug': 'Debug', 'Date and Time': 'Data e Hora',
'defines tables': 'define as tabelas', 'db': 'db',
'Delete': 'Apague', 'Debug': 'Debug',
'delete': 'apagar', 'defines tables': 'define as tabelas',
'delete all checked': 'apagar marcados', 'Delete': 'Apague',
'delete plugin': 'apagar plugin', 'delete': 'apagar',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)', 'delete all checked': 'apagar marcados',
'Delete:': 'Apague:', 'delete plugin': 'apagar plugin',
'Deploy': 'publicar', 'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Deploy on Google App Engine': 'Publicar no Google App Engine', 'Delete:': 'Apague:',
'Deploy to OpenShift': 'Deploy to OpenShift', 'Deploy': 'publicar',
'Description': 'Descrição', 'Deploy on Google App Engine': 'Publicar no Google App Engine',
'design': 'modificar', 'Deploy to OpenShift': 'Deploy to OpenShift',
'DESIGN': 'Projeto', 'Deploy to pythonanywhere': 'Deploy to pythonanywhere',
'Design for': 'Projeto de', 'Deploy to PythonAnywhere': 'Deploy to PythonAnywhere',
'Detailed traceback description': 'Detailed traceback description', 'Deployment Interface': 'Deployment Interface',
'direction: ltr': 'direção: ltr', 'Description': 'Descrição',
'Disable': 'Disable', 'design': 'modificar',
'docs': 'docs', 'DESIGN': 'Projeto',
'done!': 'feito!', 'Design for': 'Projeto de',
'download layouts': 'download layouts', 'Detailed traceback description': 'Detailed traceback description',
'Download layouts from repository': 'Download layouts from repository', 'details': 'details',
'download plugins': 'download plugins', 'direction: ltr': 'direção: ltr',
'Download plugins from repository': 'Download plugins from repository', 'Disable': 'Disable',
'E-mail': 'E-mail', 'docs': 'docs',
'EDIT': 'EDITAR', 'done!': 'feito!',
'Edit': 'editar', 'download layouts': 'download layouts',
'Edit application': 'Editar aplicação', 'Download layouts from repository': 'Download layouts from repository',
'edit controller': 'editar controlador', 'download plugins': 'download plugins',
'Edit current record': 'Editar o registro atual', 'Download plugins from repository': 'Download plugins from repository',
'Edit Profile': 'Editar Perfil', 'E-mail': 'E-mail',
'edit views:': 'editar visões:', 'EDIT': 'EDITAR',
'Editing %s': 'A Editar %s', 'Edit': 'editar',
'Editing file': 'Editando arquivo', 'Edit application': 'Editar aplicação',
'Editing file "%s"': 'Editando arquivo "%s"', 'edit controller': 'editar controlador',
'Editing Language file': 'Editando arquivo de linguagem', 'Edit current record': 'Editar o registro atual',
'Enterprise Web Framework': 'Framework web empresarial', 'Edit Profile': 'Editar Perfil',
'Error': 'Erro', 'edit views:': 'editar visões:',
'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"', 'Editing %s': 'A Editar %s',
'Error snapshot': 'Error snapshot', 'Editing file': 'Editando arquivo',
'Error ticket': 'Error ticket', 'Editing file "%s"': 'Editando arquivo "%s"',
'Errors': 'erros', 'Editing Language file': 'Editando arquivo de linguagem',
'Exception instance attributes': 'Atributos da instancia de excessão', 'Email Address': 'Email Address',
'Exit Fullscreen': 'Sair de Ecrã Inteiro', 'Enterprise Web Framework': 'Framework web empresarial',
'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)', 'Error': 'Erro',
'export as csv file': 'exportar como arquivo CSV', 'Error logs for "%(app)s"': 'Logs de erro para "%(app)s"',
'exposes': 'expõe', 'Error snapshot': 'Error snapshot',
'extends': 'estende', 'Error ticket': 'Error ticket',
'failed to reload module': 'Falha ao recarregar o módulo', 'Errors': 'erros',
'failed to reload module because:': 'falha ao recarregar o módulo por:', 'Exception instance attributes': 'Atributos da instancia de excessão',
'File': 'Arquivo', 'Exit Fullscreen': 'Sair de Ecrã Inteiro',
'file "%(filename)s" created': 'arquivo "%(filename)s" criado', 'Expand Abbreviation (html files only)': 'Expandir Abreviação (só para ficheiros html)',
'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado', 'export as csv file': 'exportar como arquivo CSV',
'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado', 'exposes': 'expõe',
'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado', 'exposes:': 'exposes:',
'file "%s" of %s restored': 'arquivo "%s" de %s restaurado', 'extends': 'estende',
'file changed on disk': 'arquivo modificado no disco', 'failed to reload module': 'Falha ao recarregar o módulo',
'file does not exist': 'arquivo não existe', 'failed to reload module because:': 'falha ao recarregar o módulo por:',
'file saved on %(time)s': 'arquivo salvo em %(time)s', 'File': 'Arquivo',
'file saved on %s': 'arquivo salvo em %s', 'file "%(filename)s" created': 'arquivo "%(filename)s" criado',
'filter': 'filtro', 'file "%(filename)s" deleted': 'arquivo "%(filename)s" apagado',
'Find Next': 'Localizar Seguinte', 'file "%(filename)s" uploaded': 'arquivo "%(filename)s" enviado',
'Find Previous': 'Localizar Anterior', 'file "%(filename)s" was not deleted': 'arquivo "%(filename)s" não foi apagado',
'First name': 'Nome', 'file "%s" of %s restored': 'arquivo "%s" de %s restaurado',
'Frames': 'Frames', 'file changed on disk': 'arquivo modificado no disco',
'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].', 'file does not exist': 'arquivo não existe',
'graph model': 'graph model', 'file saved on %(time)s': 'arquivo salvo em %(time)s',
'Group ID': 'ID do Grupo', 'file saved on %s': 'arquivo salvo em %s',
'Hello World': 'Olá Mundo', 'filter': 'filtro',
'Help': 'ajuda', 'Find Next': 'Localizar Seguinte',
'Hide/Show Translated strings': '', 'Find Previous': 'Localizar Anterior',
'htmledit': 'htmledit', 'First name': 'Nome',
'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.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.', 'Form has errors': 'Form has errors',
'Import/Export': 'Importar/Exportar', 'Frames': 'Frames',
'includes': 'inclui', 'Functions with no doctests will result in [passed] tests.': 'Funções sem doctests resultarão em testes [aceitos].',
'insert new': 'inserir novo', 'graph model': 'graph model',
'insert new %s': 'inserir novo %s', 'Group ID': 'ID do Grupo',
'inspect attributes': 'inspecionar atributos', 'Hello World': 'Olá Mundo',
'Install': 'instalar', 'Help': 'ajuda',
'Installed applications': 'Aplicações instaladas', 'Hide/Show Translated strings': '',
'internal error': 'erro interno', 'htmledit': 'htmledit',
'Internal State': 'Estado Interno', '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.': 'Se o relatório acima contém um número de ticket, isso indica uma falha no controlador em execução, antes de tantar executar os doctests. Isto acontece geralmente por erro de endentação ou erro fora do código da função.\r\nO titulo em verde indica que os testes (se definidos) passaram. Neste caso os testes não são mostrados.',
'Invalid action': 'Ação inválida', 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.': 'if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.',
'Invalid email': 'E-mail inválido', 'Import/Export': 'Importar/Exportar',
'invalid password': 'senha inválida', 'includes': 'inclui',
'Invalid Query': 'Consulta inválida', 'insert new': 'inserir novo',
'invalid request': 'solicitação inválida', 'insert new %s': 'inserir novo %s',
'invalid ticket': 'ticket inválido', 'inspect attributes': 'inspecionar atributos',
'Keyboard shortcuts': 'Atalhos de teclado', 'Install': 'instalar',
'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado', 'Installed applications': 'Aplicações instaladas',
'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados', 'internal error': 'erro interno',
'languages': 'linguagens', 'Internal State': 'Estado Interno',
'Languages': 'Linguagens', 'Invalid action': 'Ação inválida',
'languages updated': 'linguagens atualizadas', 'Invalid email': 'E-mail inválido',
'Last name': 'Sobrenome', 'invalid password': 'senha inválida',
'Last saved on:': 'Salvo em:', 'Invalid Query': 'Consulta inválida',
'License for': 'Licença para', 'invalid request': 'solicitação inválida',
'loading...': 'carregando...', 'invalid ticket': 'ticket inválido',
'locals': 'locals', 'Keyboard shortcuts': 'Atalhos de teclado',
'Login': 'Entrar', 'language file "%(filename)s" created/updated': 'arquivo de linguagem "%(filename)s" criado/atualizado',
'login': 'inicio de sessão', 'Language files (static strings) updated': 'Arquivos de linguagem (textos estáticos) atualizados',
'Login to the Administrative Interface': 'Entrar na interface adminitrativa', 'languages': 'linguagens',
'Logout': 'finalizar sessão', 'Languages': 'Linguagens',
'Lost Password': 'Senha perdida', 'languages updated': 'linguagens atualizadas',
'Manage': 'Manage', 'Last name': 'Sobrenome',
'manage': 'gerenciar', 'Last saved on:': 'Salvo em:',
'merge': 'juntar', 'License for': 'Licença para',
'Models': 'Modelos', 'lists by ticket': 'lists by ticket',
'models': 'modelos', 'Loading...': 'Loading...',
'Modules': 'Módulos', 'loading...': 'carregando...',
'modules': 'módulos', 'Local Apps': 'Local Apps',
'Name': 'Nome', 'locals': 'locals',
'new application "%s" created': 'nova aplicação "%s" criada', 'Login': 'Entrar',
'New application wizard': 'Assistente para novas aplicações ', 'login': 'inicio de sessão',
'new plugin installed': 'novo plugin instalado', 'Login successful': 'Login successful',
'New Record': 'Novo registro', 'Login to the Administrative Interface': 'Entrar na interface adminitrativa',
'new record inserted': 'novo registro inserido', 'Login/Register': 'Login/Register',
'New simple application': 'Nova aplicação básica', 'Logout': 'finalizar sessão',
'next 100 rows': 'próximos 100 registros', 'Lost Password': 'Senha perdida',
'NO': 'NÃO', 'manage': 'gerenciar',
'No databases in this application': 'Não existem bancos de dados nesta aplicação', 'Manage': 'Manage',
'no match': 'não encontrado', 'merge': 'juntar',
'no package selected': 'nenhum pacote selecionado', 'models': 'modelos',
'online designer': 'online designer', 'Models': 'Modelos',
'or alternatively': 'or alternatively', 'Modules': 'Módulos',
'Or Get from URL:': 'Ou Obtenha do URL:', 'modules': 'módulos',
'or import from csv file': 'ou importar de um arquivo CSV', 'Name': 'Nome',
'or provide app url:': 'ou forneça a url de uma aplicação:', 'new application "%s" created': 'nova aplicação "%s" criada',
'or provide application url:': 'ou forneça a url de uma aplicação:', 'New Application Wizard': 'New Application Wizard',
'Origin': 'Origem', 'New application wizard': 'Assistente para novas aplicações ',
'Original/Translation': 'Original/Tradução', 'new plugin installed': 'novo plugin instalado',
'Overwrite installed app': 'sobrescrever aplicação instalada', 'New Record': 'Novo registro',
'Pack all': 'criar pacote', 'new record inserted': 'novo registro inserido',
'Pack compiled': 'criar pacote compilado', 'New simple application': 'Nova aplicação básica',
'Pack custom': 'Pack custom', 'next 100 rows': 'próximos 100 registros',
'pack plugin': 'empacotar plugin', 'NO': 'NÃO',
'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui', 'No databases in this application': 'Não existem bancos de dados nesta aplicação',
'Password': 'Senha', 'no match': 'não encontrado',
'password changed': 'senha alterada', 'no package selected': 'nenhum pacote selecionado',
'Peeking at file': 'Visualizando arquivo', 'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado', 'online designer': 'online designer',
'Plugin "%s" in application': 'Plugin "%s" na aplicação', 'or alternatively': 'or alternatively',
'plugins': 'plugins', 'Or Get from URL:': 'Ou Obtenha do URL:',
'Plugins': 'Plugins', 'or import from csv file': 'ou importar de um arquivo CSV',
'Plural-Forms:': 'Plural-Forms:', 'or provide app url:': 'ou forneça a url de uma aplicação:',
'Powered by': 'Este site utiliza', 'or provide application url:': 'ou forneça a url de uma aplicação:',
'previous 100 rows': '100 registros anteriores', 'Origin': 'Origem',
'Private files': 'Private files', 'Original/Translation': 'Original/Tradução',
'private files': 'private files', 'Overwrite installed app': 'sobrescrever aplicação instalada',
'Query:': 'Consulta:', 'Pack all': 'criar pacote',
'Rapid Search': 'Rapid Search', 'Pack compiled': 'criar pacote compilado',
'record': 'registro', 'Pack custom': 'Pack custom',
'record does not exist': 'o registro não existe', 'pack plugin': 'empacotar plugin',
'record id': 'id do registro', 'PAM authenticated user, cannot change password here': 'usuario autenticado por PAM, não pode alterar a senha por aqui',
'Record ID': 'ID do Registro', 'Password': 'Senha',
'Register': 'Registrar-se', 'password changed': 'senha alterada',
'Registration key': 'Chave de registro', 'Peeking at file': 'Visualizando arquivo',
'Reload routes': 'Reload routes', 'Please wait, giving pythonanywhere a moment...': 'Please wait, giving pythonanywhere a moment...',
'Remove compiled': 'eliminar compilados', 'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" eliminado',
'Replace': 'Substituir', 'Plugin "%s" in application': 'Plugin "%s" na aplicação',
'Replace All': 'Substituir Tudo', 'plugins': 'plugins',
'request': 'request', 'Plugins': 'Plugins',
'Resolve Conflict file': 'Arquivo de resolução de conflito', 'Plural-Forms:': 'Plural-Forms:',
'response': 'response', 'Powered by': 'Este site utiliza',
'restore': 'restaurar', 'previous 100 rows': '100 registros anteriores',
'revert': 'reverter', 'Private files': 'Private files',
'Role': 'Papel', 'private files': 'private files',
'Rows in table': 'Registros na tabela', 'PythonAnywhere Apps': 'PythonAnywhere Apps',
'Rows selected': 'Registros selecionados', 'PythonAnywhere Password': 'PythonAnywhere Password',
'rules are not defined': 'rules are not defined', 'Query:': 'Consulta:',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')", 'Rapid Search': 'Rapid Search',
'Running on %s': 'A correr em %s', 'Read': 'Read',
'Save': 'Save', 'record': 'registro',
'save': 'salvar', 'record does not exist': 'o registro não existe',
'Save file:': 'Gravar ficheiro:', 'record id': 'id do registro',
'Save file: %s': 'Gravar ficheiro: %s', 'Record ID': 'ID do Registro',
'Save via Ajax': 'Gravar via Ajax', 'Register': 'Registrar-se',
'Saved file hash:': 'Hash do arquivo salvo:', 'Registration key': 'Chave de registro',
'selected': 'selecionado(s)', 'Reload routes': 'Reload routes',
'session': 'session', 'Remove compiled': 'eliminar compilados',
'session expired': 'sessão expirada', 'Replace': 'Substituir',
'shell': 'Terminal', 'Replace All': 'Substituir Tudo',
'Site': 'site', 'request': 'request',
'some files could not be removed': 'alguns arquicos não puderam ser removidos', 'requires python-git, but not installed': 'requires python-git, but not installed',
'Start searching': 'Start searching', 'Resolve Conflict file': 'Arquivo de resolução de conflito',
'Start wizard': 'iniciar assistente', 'response': 'response',
'state': 'estado', 'restore': 'restaurar',
'Static': 'Static', 'revert': 'reverter',
'static': 'estáticos', 'Role': 'Papel',
'Static files': 'Arquivos estáticos', 'Rows in table': 'Registros na tabela',
'Submit': 'Submit', 'Rows selected': 'Registros selecionados',
'submit': 'enviar', 'rules are not defined': 'rules are not defined',
'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?', "Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'table': 'tabela', 'Running on %s': 'A correr em %s',
'Table name': 'Nome da tabela', 'Save': 'Save',
'test': 'testar', 'save': 'salvar',
'Testing application': 'Testando a aplicação', 'Save file:': 'Gravar ficheiro:',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.', 'Save file: %s': 'Gravar ficheiro: %s',
'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador', 'Save via Ajax': 'Gravar via Ajax',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller', 'Saved file hash:': 'Hash do arquivo salvo:',
'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados', 'selected': 'selecionado(s)',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets', 'session': 'session',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates', 'session expired': 'sessão expirada',
'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates', 'shell': 'Terminal',
'There are no controllers': 'Não existem controllers', 'Site': 'site',
'There are no models': 'Não existem modelos', 'some files could not be removed': 'alguns arquicos não puderam ser removidos',
'There are no modules': 'Não existem módulos', 'Something went wrong please wait a few minutes before retrying': 'Something went wrong please wait a few minutes before retrying',
'There are no plugins': 'There are no plugins', 'source : filesystem': 'source : filesystem',
'There are no private files': '', 'Start a new app': 'Start a new app',
'There are no static files': 'Não existem arquicos estáticos', 'Start searching': 'Start searching',
'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada', 'Start wizard': 'iniciar assistente',
'There are no views': 'Não existem visões', 'state': 'estado',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app', 'Static': 'Static',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here', 'static': 'estáticos',
'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui', 'Static files': 'Arquivos estáticos',
'This is the %(filename)s template': 'Este é o template %(filename)s', 'Submit': 'Submit',
'Ticket': 'Ticket', 'submit': 'enviar',
'Ticket ID': 'Ticket ID', 'Sure you want to delete this object?': 'Tem certeza que deseja apaagr este objeto?',
'Timestamp': 'Data Atual', 'switch to : db': 'switch to : db',
'TM': 'MR', 'table': 'tabela',
'to previous version.': 'para a versão anterior.', 'Table name': 'Nome da tabela',
'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]', 'test': 'testar',
'toggle breakpoint': 'toggle breakpoint', 'Testing application': 'Testando a aplicação',
'Toggle comment': 'Toggle comment', 'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'A "consulta" é uma condição como "db.tabela.campo1==\'valor\'". Algo como "db.tabela1.campo1==db.tabela2.campo2" resulta em um JOIN SQL.',
'Toggle Fullscreen': 'Toggle Fullscreen', 'the application logic, each URL path is mapped in one exposed function in the controller': 'A lógica da aplicação, cada URL é mapeada para uma função exposta pelo controlador',
'Traceback': 'Traceback', 'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'translation strings for the application': 'textos traduzidos para a aplicação', 'the data representation, define database tables and sets': 'A representação dos dadps, define tabelas e estruturas de dados',
'Translation strings for the application': 'Translation strings for the application', 'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'try': 'tente', 'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'try something like': 'tente algo como', 'the presentations layer, views are also known as templates': 'A camada de apresentação, As visões também são chamadas de templates',
'Try the mobile interface': 'Try the mobile interface', 'There are no controllers': 'Não existem controllers',
'Unable to check for upgrades': 'Não é possível checar as atualizações', 'There are no models': 'Não existem modelos',
'unable to create application "%s"': 'não é possível criar a aplicação "%s"', 'There are no modules': 'Não existem módulos',
'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"', 'There are no plugins': 'There are no plugins',
'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"', 'There are no private files': '',
'Unable to download': 'Não é possível efetuar o download', 'There are no static files': 'Não existem arquicos estáticos',
'Unable to download app': 'Não é possível baixar a aplicação', 'There are no translators, only default language is supported': 'Não há traduções, somente a linguagem padrão é suportada',
'Unable to download app because:': 'Não é possível baixar a aplicação porque:', 'There are no views': 'Não existem visões',
'Unable to download because': 'Não é possível baixar porque', 'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'unable to parse csv file': 'não é possível analisar o arquivo CSV', 'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'unable to uninstall "%s"': 'não é possível instalar "%s"', 'these files are served without processing, your images go here': 'Estes arquivos são servidos sem processamento, suas imagens ficam aqui',
'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"', 'This is the %(filename)s template': 'Este é o template %(filename)s',
'uncheck all': 'desmarcar todos', 'Ticket': 'Ticket',
'Uninstall': 'desinstalar', 'Ticket ID': 'Ticket ID',
'update': 'atualizar', 'Timestamp': 'Data Atual',
'update all languages': 'atualizar todas as linguagens', 'TM': 'MR',
'Update:': 'Atualizar:', 'to previous version.': 'para a versão anterior.',
'upgrade web2py now': 'atualize o web2py agora', 'To create a plugin, name a file/folder plugin_[name]': 'Para criar um plugin, nomeio um arquivo/pasta como plugin_[nome]',
'upload': 'upload', 'toggle breakpoint': 'toggle breakpoint',
'Upload': 'Upload', 'Toggle comment': 'Toggle comment',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada', 'Toggle Fullscreen': 'Toggle Fullscreen',
'Upload a package:': 'Faça upload de um pacote:', 'Traceback': 'Traceback',
'Upload and install packed application': 'Upload and install packed application', 'translation strings for the application': 'textos traduzidos para a aplicação',
'upload application:': 'Fazer upload de uma aplicação:', 'Translation strings for the application': 'Translation strings for the application',
'Upload existing application': 'Faça upload de uma aplicação existente', 'try': 'tente',
'upload file:': 'Enviar arquivo:', 'try something like': 'tente algo como',
'upload plugin file:': 'Enviar arquivo de plugin:', 'Try the mobile interface': 'Try the mobile interface',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.', 'Unable to check for upgrades': 'Não é possível checar as atualizações',
'Use an url:': 'Use uma url:', 'unable to create application "%s"': 'não é possível criar a aplicação "%s"',
'User ID': 'ID do Usuario', 'unable to delete file "%(filename)s"': 'não é possível criar o arquico "%(filename)s"',
'variables': 'variáveis', 'unable to delete file plugin "%(plugin)s"': 'não é possível criar o plugin "%(plugin)s"',
'Version': 'Versão', 'Unable to download': 'Não é possível efetuar o download',
'versioning': 'versionamento', 'Unable to download app': 'Não é possível baixar a aplicação',
'Versioning': 'Versioning', 'Unable to download app because:': 'Não é possível baixar a aplicação porque:',
'view': 'visão', 'Unable to download because': 'Não é possível baixar porque',
'Views': 'Visões', 'unable to parse csv file': 'não é possível analisar o arquivo CSV',
'views': 'visões', 'unable to uninstall "%s"': 'não é possível instalar "%s"',
'Web Framework': 'Web Framework', 'unable to upgrade because "%s"': 'não é possível atualizar porque "%s"',
'web2py is up to date': 'web2py está atualizado', 'uncheck all': 'desmarcar todos',
'web2py Recent Tweets': 'Tweets Recentes de @web2py', 'Uninstall': 'desinstalar',
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar', 'update': 'atualizar',
'Welcome to web2py': 'Bem-vindo ao web2py', 'update all languages': 'atualizar todas as linguagens',
'YES': 'SIM', 'Update:': 'Atualizar:',
} 'upgrade now to %s': 'upgrade now to %s',
'upgrade web2py now': 'atualize o web2py agora',
'upload': 'upload',
'Upload': 'Upload',
'Upload & install packed application': 'Faça upload e instale uma aplicação empacotada',
'Upload a package:': 'Faça upload de um pacote:',
'Upload and install packed application': 'Upload and install packed application',
'upload application:': 'Fazer upload de uma aplicação:',
'Upload existing application': 'Faça upload de uma aplicação existente',
'upload file:': 'Enviar arquivo:',
'upload plugin file:': 'Enviar arquivo de plugin:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para criar consultas mais complexas.',
'Use an url:': 'Use uma url:',
'User ID': 'ID do Usuario',
'Username': 'Username',
'variables': 'variáveis',
'Version': 'Versão',
'versioning': 'versionamento',
'Versioning': 'Versioning',
'view': 'visão',
'Views': 'Visões',
'views': 'visões',
'Warning!': 'Warning!',
'Web Framework': 'Web Framework',
'web2py Admin Password': 'web2py Admin Password',
'web2py is up to date': 'web2py está atualizado',
'web2py Recent Tweets': 'Tweets Recentes de @web2py',
'web2py upgraded; please restart it': 'web2py atualizado; favor reiniciar',
'Welcome to web2py': 'Bem-vindo ao web2py',
'YES': 'SIM',
'You only need these if you have already registered': 'You only need these if you have already registered',
}
+5 -10
View File
@@ -12,29 +12,24 @@ def button(href, label):
if is_mobile: if is_mobile:
ret = A_button(SPAN(label), _href=href) ret = A_button(SPAN(label), _href=href)
else: else:
ret = A(SPAN(label), _class='button btn', _href=href) ret = A(SPAN(label), _class='btn rounded', _href=href)
return ret return ret
def button_enable(href, app): def button_enable(href, app):
if os.path.exists(os.path.join(apath(app, r=request), 'DISABLED')): if os.path.exists(os.path.join(apath(app, r=request), 'DISABLED')):
label = SPAN(T('Enable'), _style='color:red') text, classes = T("Enable"), "btn rounded red"
else: else:
label = SPAN(T('Disable'), _style='color:green') text, classes = T("Disable"), "btn rounded gree"
id = 'enable_' + app id = 'enable_' + app
return A(label, _class='button btn', _id=id, callback=href, target=id) return A(text, _class=classes, _id=id, callback=href, target=id)
def sp_button(href, label): def sp_button(href, label):
if request.user_agent().get('is_mobile'): if request.user_agent().get('is_mobile'):
ret = A_button(SPAN(label), _href=href) ret = A_button(SPAN(label), _href=href)
else: else:
ret = A(SPAN(label), _class='button special btn btn-inverse', _href=href) ret = A(SPAN(label), _class='btn pink rounded', _href=href)
return ret return ret
def helpicon(): def helpicon():
return IMG(_src=URL('static', 'images/help.png'), _alt='help') return IMG(_src=URL('static', 'images/help.png'), _alt='help')
def searchbox(elementid):
return SPAN(LABEL(IMG(_id="search_start", _src=URL('static', 'images/search.png'), _alt=T('filter')),
_class='icon', _for=elementid), ' ',
INPUT(_id=elementid, _type='text', _size=12, _class="input-medium"),
_class="searchbox")
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-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;
}
}
-489
View File
@@ -1,489 +0,0 @@
/*=============================================================
GENERAL
==============================================================*/
body { /*remember to account for the hidden area underneath
fixed navbar by adding at least 40px padding to the <body>.
Be sure to add this after the core Bootstrap CSS
and before the optional responsive CSS.
An alternative solution is to set top-margin to div#main padding-top:60px; comment this for alternative solution*/ height:auto; /*uncomment this for alternative solution*/ }
/*=============================================================
BOOTSTRAP ICONS FOLDER FIX
==============================================================*/
[class^="icon-"], [class*=" icon-"] { /* right folder for bootstrap black images/icons*/ background-image:url("../images/glyphicons-halflings.png") }
.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-"] { /* right folder for bootstrap white images/icons*/ background-image:url("../images/glyphicons-halflings-white.png"); }
/*=============================================================
INPUT BORDER HIGHLIGHT WHEN INPUT IS FOCUSED
==============================================================*/
textarea:focus, input[type="text"]:focus, input[type="password"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="date"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="color"]:focus, input[type="file"]:focus, select:focus, .uneditable-input:focus { /* outline color*/ border-color:rgba(232, 149, 60, 0.8); outline:0; /*outline:thin dotted \9;*/ -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_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; }
/*=============================================================
COLOR OF LINKS
==============================================================*/
a, a:hover { color:#E8953C; text-decoration:none; }
a:hover { color:#e2821b; }
/*=============================================================
CONTROLS and CONTAINERS
==============================================================*/
.row-buttons .btn { margin-bottom:7px; }
.sidebar .box { clear:right; margin-top:2em; border-top:1px solid #d1d1d1; padding:0 1em; }
.pwdchange>.button { margin-bottom:10px; }
input[type="file"] { margin-bottom:9px; }
.form-inline input[type="file"] { margin-bottom:0px; }
input + .help-block { margin-top:-10px; margin-bottom:4px; }
#confirm_form input.btn, .generatedbyw2p input { margin-right:4px; }
a[rel='tooltip'] span, div[rel='tooltip'] span { display:none; margin-left:-9999px; }
/*in-page browsing*/
[rel="pagebookmark"] { position:relative; }
[rel="pagebookmark"]>.component { cursor:pointer; }
[rel="pagebookmark"]>.hashstick { position:absolute; top:-54px; left:-9999px; visibility:visible; }
/* following 2 rules set the style of a small button for going to top of page*/
.tophashlink.btn { padding:2px 3px; visibility:hidden; }
.hashstick:target+.tophashlink.btn { visibility:visible; }
ul.act_edit { margin-top:4px; margin-left:20px; }
ul.act_edit .btn { margin-top:4px; margin-bottom:4px; }
ul.act_edit .file>a { white-space:pre; }
.right-full { text-align:right; }
.searchbox, .searchbox label, .searchbox input { display:inline-block; }
.buttons-row .btn { margin-bottom:9px; }
.li-controls { display:inline-block; width:180px; vertical-align:middle; }
.celled { display:inline-block; padding: 0 0 0 4px; vertical-align:top; margin-top:4px; width:700px; }
.folder { list-style-type:none; #border-left: 1px dotted #AAA; }
.folder li { list-style-type:none; }
.folder>i { display:inline-block; width:5px; height:5px; border:1px solid; background-color:#FAA732; margin-left:-4px; margin-top:-2px; border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-radius:1px; }
.folder>i+a { padding-left:0.5em; }
.folder ul { margin-top:0.5em; margin-bottom:0.5em; }
.controls-inline .btn { margin-right:5px; }
div.web2py_counter.span6 { min-height:24px; text-align:right; }
.pagination { margin:0; }
.table { margin-bottom:10px; }
.row_buttons .btn { margin-right:4px; }
.editor-bar-column { display:inline-block; vertical-align:top; margin-right:4px; }
.editor-bar-column .input-long { width:270px; }
.editor-bar-column .input-normal { width:206px; }
.keybindings li { margin-bottom:0.5em; }
.keybindings span { padding:0.3em; border:1px solid transparent; vertical-align:middle; }
.teletype-text { font-family:monospace; font-weight:bold; font-style:normal; border-color:#999; background:#333; color:#DDD; -moz-border-radius:0.3em; border-radius:0.3em; }
.edit_language .tab_row div { display:inline-block; vertical-align:top; margin-right:4px; }
.edit_language .fake-input { height:18px; padding:4px; font-size:13px; line-height:18px; overflow:hidden; white-space:nowrap; display:inline-block; margin-bottom:9px; }
.test h3 { padding-left:9px; margin:0; font-size:16px; line-height:1; border-left:9px solid transparent; }
.test h3.passed { border-color:#009900; }
.test h3.failed { border-color:#CC0000; }
.test h3.nodoctests { border-color:#CCCC99; }
.test .test_report { width:100%; overflow:auto; }
.test_report pre { white-space:pre; }
.test div[id^="output_"]>h2 { font-size:18px; line-height:1; color:grey; }
div.center { text-align:center; }
.delete h2 { word-wrap:break-word; }
/*=============================================================
SHELL
==============================================================*/
.shell .output-wrapper { width:100%; height:30em; border:1px solid #333; }
.shell .prompt-wrapper { float:left; width:100%; overflow:hidden; height:auto; border:1px solid #333; }
.shell .prompt-container { margin-left:2.5em; }
.shell #caret { width:2.5em; float:left; margin-left:-100%; }
.shell #shellwrapper { background:white; color:#E8953C; width:100%; margin:1em 0; border:0; }
.shell #output, .shell .prompt { color:#E8953C; background:white; resize:none; border:none; width:100%; height:100%; cursor:default; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
.shell #output:focus, .shell .prompt:focus { border-color:transparent; outline:0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; }
.shell #output pre { color: #E8953C; }
.shell #autoscroll { cursor:pointer; float:right; }
.shell .prompt, .shell #output, .shell #caret { font-size: 11pt; padding: 6px; padding-right: 0em; }
.shell #caret { padding-top:9px; }
.shell .prompt, .shell #output, .shell pre, .shell #caret { font-family: monospace; }
.shell a[rel="tooltip"] { margin-left:8px; }
/*=============================================================
PEEK
==============================================================*/
.peek .code-wrapper { width:100%; overflow:auto; white-space:pre; }
.peek table td pre { word-break:normal; white-space:pre; }
/*=============================================================
FOOTER
==============================================================*/
#footer { border-top:1px solid; text-align:center; padding:1em 0; }
#footer span, #footer select { display:inline-block; margin-bottom:0; vertical-align:middle; }
#footer select { width:auto; }
/*=============================================================
MAIN
==============================================================*/
#main { margin-top:60px; /*uncomment this for alternative solution to hidden area underneath fixed navbar issue*/ margin-bottom:60px; }
/*=============================================================
WIZARD
==============================================================*/
#wizard_nav .box { border-bottom:1px dotted; }
#wizard_nav li { margin-left:1em; margin-top:0.5em; }
.step textarea { width:auto; }
select[name='layout_theme'] { vertical-align:top; }
img#preview { margin-bottom:9px; }
/* multiselect customization*/
.ms-container { margin-bottom:5px; }
.ms-selectable, .step .ms-selection { text-align:center; }
.ms-list { text-align:left; background:white; }
.ms-container li.ms-elem-selectable:not(.disabled).ms-hover, .ms-container .ms-selection li:not(.disabled).ms-hover { background-color:#E8953C; }
.ms-container .ms-selectable { margin-right:25px; }
.ms-container .ms-selectable, .ms-container .ms-selection { background:transparent; }
.ms-container .ms-list.ms-focus { border-color:rgba(232, 149, 60, 0.8); -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); }
/* grow_input*/
ul[id$="_grow_input"] { margin-left:0; }
/* generate_form*/
#generate_form .control-group { margin-bottom:0; }
#generate_form .control-label { text-align:left; }
#generate_form .controls { padding-left:18px; margin-left:0; }
#generate_form .control-label.empty { width:142px; }
.step [rel="pagebookmark"]>.hashstick { display:none; }
/*generated page*/
.generated iframe { border:1px inset #e3e3e3; }
/*=============================================================
ERRORS TABLE / TICKET PAGE
==============================================================*/
.tablebar { margin:7px 0 7px 0; }
.tablebar input { margin-right:27px; }
.tablebar span { vertical-align:bottom; }
.table th { background: #e9e9e9; background: -moz-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #FAFAFA), color-stop(100%, #E9E9E9)); background: -webkit-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -o-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: -ms-linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); background: linear-gradient(top, #FAFAFA 0%, #E9E9E9 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#FAFAFA', endColorstr='#E9E9E9')"; /*font-size:10px; color:#444; text-transform:uppercase;*/ }
td.cbcentered, th.cbcentered { text-align:center; }
td.cbcentered>input, th.cbcentered>input { margin-top:-1px; }
.traceback div { }
.ticket_code>table td:first-child { border-left:0; }
#trck_errors table td pre { word-break:normal; white-space:pre; }
.inspect pre, .errorsource pre { word-break:normal; white-space:pre; }
.ticket_code { background-color:lightyellow; }
.ticket_code table, .ticket_code td { border-width:0px; border-collapse:collapse; width:100%; }
.ticket_code tbody tr:hover td { background-color:transparent; }
/*=============================================================
FLOT GRAPHS
==============================================================*/
.about #placeholder { width:auto; max-width:600px; height:300px; position:relative; margin:0 auto; /* for centering*/ }
/*=============================================================
THE GRID
==============================================================*/
#w2p_query_panel { min-width:20px; min-height:20px; padding:10px; margin-top:1em; background-color:#f5f5f5; border: 1px solid #e3e3e3; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); }
#w2p_query_panel select, #w2p_query_panel input { margin-bottom:0; margin-right:4px; }
.web2py_grid .hidden { visibility:visible; }
.qry_pnl_btns { display:inline-block; }
#w2p_grid_addbtn, #w2p_search-form { margin-top:9px; margin-bottom:9px; }
#w2p_search-form { margin-bottom:0; }
#w2p_search-form form { margin-bottom:0; }
/*----- translate page ---*/
.languageform input { margin-bottom:0; }
.languageform input.untranslated { background-color:#FC0; }
/*=============================================================
MASKED UPLOAD INPUT (NO BOOTSTRAP RELATED)
==============================================================*/
#appupdate_file.masked {
margin: 0;
opacity: 0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* IE 8 */
filter: alpha(opacity=0); /* IE 7 */
font-size: 100px;
position: absolute;
top: 0;
right: 0;
z-index: 410;
}
#fileselect {
padding: 4px 6px;
border: 1px solid #ccc;
border-radius: 4px;
color: #555;
cursor: default;
position: relative;
z-index: 400;
font-size: 14px;
background-color: #fff;
margin-bottom: 10px;
overflow: hidden;
}
#fileselect span {
position: absolute;
left: 6px;
top: 4px;
}
.uploadbtn {
position: absolute;
top: 3px;
right: 3px;
}
.txtPlaceholder {
font-style: italic;
color: #ccc;
}
/*=============================================================
EDIT PAGE SLIDING FILES MENU
==============================================================*/
@media (max-width: 979px) {
body.edit div#header {position:relative; z-index: 1030 !important;}
}
#editor_area, #edit_placeholder {
margin: 0;
padding: 0;
}
#editor_area {
position: relative;
box-sizing: border-box;
}
#files {
width: auto;
height: 100%;
margin: 0;
padding: 0;
position: fixed;
top: 0px;
left: 0px;
z-index: 1031;
border-right: 3px solid #000;
/* animation (it doesn't work in IE<10) */
-moz-transition: all 0.4s;
-webkit-transition: all 0.4s;
-o-transition: all 0.4s;
transition: all 0.4s;
}
#files:hover, #files:focus {
left: 0px !important;
}
#files, .files-toggle {
background: #1b1b1b;
opacity: 0.98;
}
.files-toggle {
width: 18px;
height: 86px;
border-radius: 0px 4px 4px 0px;
color: #999;
position: absolute;
top: 60px;
right: -18px;
cursor: default;
}
.arrow {
display: block;
position: absolute;
top: 8px;
width: 18px;
height: 70px;
background: url(../images/files_toggle.png) no-repeat;
}
.files-menu {
height: 100%;
overflow: auto;
}
#filelist {
position: relative;
top: 60px;
padding-bottom: 60px;
}
#filelist li {
padding-right: 8px;
width: 100%;
}
#filelist li>a {
text-shadow: none;
}
/*=============================================================
MEDIA QUERIES
==============================================================*/
@media (max-width: 800px) { .step [rel="pagebookmark"]>.hashstick { /*top:-54px;*/ display:block; }
}
@media (max-width: 767px) { [rel="pagebookmark"]>.hashstick { top:0; }
/*-----------------------------------
main
-------------------------------------*/
#main { margin-top:0; }
/*-----------------------------------
footer
-------------------------------------*/
#footer { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
/*-----------------------------------
errors page
-------------------------------------*/
#trck_errors { table-layout:fixed; }
#trck_errors .column1 { width:20px; }
#trck_errors .column2 { width:45px; }
#trck_errors .column3 { width:150px; }
#trck_errors .columnN { width:55px; }
#trck_errors .columnN1 { width:138px; }
.ticket_code, .inspect.resp1, .inspect.controls pre, .errorsource { width:100%; overflow:auto; }
.ticket_code>table { width:100%; }
.celled { width:320px; }
}
@media (max-width: 480px) { .qry_pnl_btns { display:block; margin-top:4px; }
/*-----------------------------------
wizard
-------------------------------------*/
#generate_form .control-label { float:left; width:160px; padding-top:5px; }
.inspect>code { display:block; white-space:normal; }
.li-controls { }
.celled { width:165px; }
}
/*-----------------------------------
miscellaneous
-------------------------------------*/
h4.editableapp, h4.currentapp { padding: 5px 0 5px 54px; display: inline; }
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; }
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; }
table.twitter{ background-color: transparent; }
table.twitter tr td {vertical-align: top; padding: 5px; }
table.twitter tr { border-bottom: 1px solid #a0a0a0; }
div.error_wrapper {margin-top:-10px; margin-bottom:8px; padding-left:2px; color:#d62e2b; }
.twitter-timeline >iframe{padding: 1em 0;}
+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; .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}
border-radius: 10px; .calendar table {border-collapse:collapse}
-moz-border-radius: 10px; .calendar tbody tr:hover {background-color:#fbf6d9}
-webkit-border-radius: 10px; .calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
}.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 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}
+361
View File
@@ -0,0 +1,361 @@
/************
Created by Massimo Di Pierro
Stupid.css is what the names says, take it with a grain of salt
License: BSD
************/
/*** basic styles ***/
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
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:strong; 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:string; 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; line-height:2.4em; 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; line-height:1.5em}
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
.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; width:100%}
.yscroll {overflow-y:scroll; width:100%}
.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=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}
input, textarea, select, button {font-size:12px}
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; 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:2000}
.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; z-index:2000}
}
@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]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[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:5px;
margin-top:1em;
}
[data-tooltip]:hover:after {
background-color:rgba(0,0,0,0.8) !important;
border:4px solid rgba(0,0,0,0.8) !important;
border-radius:7px !important;
color:white !important;
content:attr(data-tooltip);
text-transform:none;
font-size: 11px;
left:0 !important;
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, .tags > span.off:hover {
height: 30px;
padding: 4px 9px;
text-decoration: none;
margin: 0 5px 30px 0 !important;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
line-height: 32px;
}
.tags.dismissible > span:not(.off):hover {
background-color: #ccc !important;
}
.tags.dismissible > span:not(.off):after {
content: " \f00d";
font-family: FontAwesome;
}
.tags > span.off {
background-color: #ccc;
}
+21 -139
View File
@@ -1,84 +1,17 @@
/** these MUST stay **/ header a {color: white; font-size:1.1em}
a {text-decoration:none; white-space:nowrap} main {min-height: 70vh}
a:hover {text-decoration:underline} .form-group {padding-bottom: 10px !important;}
a.button {text-decoration:none} .w2p_hidden {display:none;visibility:visible}
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} .right {float:right; text-align:right}
.left {float:left; text-align:left} .left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle} .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_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top} td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:left} td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px} td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px} td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0} 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;} 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} */ /* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em} #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_user_form td {vertical-align:top}
/*********** web2py specific ***********/ /*********** web2py specific ***********/
div.flash { div.w2p_flash {
font-weight:bold; font-weight:bold;
display:none; display:none;
position:fixed; padding:20px 20px 20px 50px;
padding:10px; width:100%;
top:48px;
right:250px;
min-width:280px;
opacity:0.95; opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle; vertical-align:middle;
cursor:pointer; cursor:pointer;
color:#fff; color:#000;
background-color:#000; background-color:#ffdc00;
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; 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 .ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;} {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_wrapper {display:block}
div.error { div.error {
width: 298px; color:red;
background:red;
border: 2px solid #d00;
color:white;
padding:5px; padding:5px;
display:inline-block; 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 { .topbar {
@@ -190,34 +99,8 @@ div.error {
*/ */
/* .web2py_table {border:1px solid #ccc} */ /* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {} .web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%} .web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;} .web2py_grid td {color: black;}
.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 { .web2py_console form {
width: 100%; width: 100%;
@@ -302,11 +185,6 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select, .web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; } .web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#wiki_page_body { #wiki_page_body {
width: 600px; width: 600px;
@@ -317,6 +195,10 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */ /* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2} .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.w2p_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:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px} .ie9 #w2p_query_panel {padding-bottom:2px}
.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

+4 -4
View File
@@ -77,10 +77,10 @@ function doClickSave() {
t.attr('disabled', ''); t.attr('disabled', '');
var flash = xhr.getResponseHeader('web2py-component-flash'); var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) { if(flash) {
$('.flash').html(decodeURIComponent(flash)) $('.w2p_flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>') .append('<a href="#" class="close">&times;</a>')
.slideDown(); .slideDown();
} else $('.flash').hide(); } else $('.w2p_flash').hide();
try { try {
if(json.error) { if(json.error) {
window.location.href = json.redirect; window.location.href = json.redirect;
@@ -158,10 +158,10 @@ function doToggleBreakpoint(filename, url, sel) {
// show flash message (if any) // show flash message (if any)
var flash = xhr.getResponseHeader('web2py-component-flash'); var flash = xhr.getResponseHeader('web2py-component-flash');
if(flash) { if(flash) {
$('.flash').html(decodeURIComponent(flash)) $('.w2p_flash').html(decodeURIComponent(flash))
.append('<a href="#" class="close">&times;</a>') .append('<a href="#" class="close">&times;</a>')
.slideDown(); .slideDown();
} else $('.flash').hide(); } else $('.w2p_flash').hide();
try { try {
if(json.error) { if(json.error) {
window.location.href = json.redirect; window.location.href = json.redirect;
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');});
});
+8 -8
View File
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}} dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="all_keys"> <div class="w2p_hidden" id="all_keys">
{{=total['keys']}} {{=total['keys']}}
</div> </div>
<br /> <br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}} dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="ram_keys"> <div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}} {{=ram['keys']}}
</div> </div>
<br /> <br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}} dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="disk_keys"> <div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}} {{=disk['keys']}}
</div> </div>
<br /> <br />
@@ -249,8 +249,8 @@
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul> </ul>
</div> </div>
<br /> <br />
+1 -2
View File
@@ -1,5 +1,4 @@
{{extend 'layout.html'}} {{extend 'layout.html'}}
{{block sectionclass}}about{{end}}
<!-- begin "about" block --> <!-- begin "about" block -->
<h2>{{=T("About application")}} "{{=app}}"</h2> <h2>{{=T("About application")}} "{{=app}}"</h2>
<h3>{{=T("About")}} {{=app}}</h3> <h3>{{=T("About")}} {{=app}}</h3>
@@ -23,4 +22,4 @@ jQuery(document).ready(function() {
jQuery.plot(jQuery("#placeholder"), [ {{=progress}} ]); jQuery.plot(jQuery("#placeholder"), [ {{=progress}} ]);
}) })
</script> </script>
<!-- end "about" block --> <!-- end "about" block -->
+269 -272
View File
@@ -9,19 +9,16 @@ def peekfile(path,file,vars={},title=None):
return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars)) return A(file.replace('\\\\','/'),_title=title,_href=URL('peek', args=args, vars=vars))
def editfile(path,file,vars={}): def editfile(path,file,vars={}):
args=(path,file) if 'app' in vars else (app,path,file) args=(path,file) if 'app' in vars else (app,path,file)
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit', args=args, vars=vars)) return A(T('Edit'),_class='btn small rounded black',_href=URL('edit', args=args, vars=vars))
def testfile(path,file): def testfile(path,file):
return A(TAG[''](IMG(_src=URL('static', 'images/test_icon.png'), _alt=T('test')), return A(I(_class="fa fa-cog"),**{
SPAN(T("Run tests in this file (to run all files, you may also use the button labelled 'test')"))), '_class':'btn small rounded black',
_class='icon test', '_data-tooltip':T("Run tests in this file (to run all files, you may also use the but\
_href=URL('test', args=(app, file)), ton labelled 'test')")})
_rel="tooltip",
**{'_data-placement':'right',
'_data-original-title':T("Run tests in this file (to run all files, you may also use the button labelled 'test')")})
def editlanguagefile(path,file,vars={}): def editlanguagefile(path,file,vars={}):
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_language', args=(app, path, file), vars=vars)) return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_language', args=(app, path, file), vars=vars))
def editpluralsfile(path,file,vars={}): def editpluralsfile(path,file,vars={}):
return A(SPAN(T('Edit')),_class='button editbutton btn btn-mini',_href=URL('edit_plurals', args=(app, path, file), vars=vars)) return A(T('Edit'),_class='button editbutton btn rounded small',_href=URL('edit_plurals', args=(app, path, file), vars=vars))
def file_upload_form(location, anchor=None): def file_upload_form(location, anchor=None):
form=FORM( form=FORM(
LABEL(T("upload file:")), LABEL(T("upload file:")),
@@ -59,13 +56,8 @@ def upload_plugin_form(app, anchor=None):
return form return form
def deletefile(arglist, vars={}): def deletefile(arglist, vars={}):
vars.update({'sender':request.function+'/'+app}) vars.update({'sender':request.function+'/'+app})
return A(TAG[''](IMG(_src=URL('static', 'images/delete_icon.png')), return A(I(_class='fa fa-trash'),_class="red rounded small btn",
SPAN(T('Delete this file (you will be asked to confirm deletion)'))), _href=URL('delete',args=arglist,vars=vars))
_href=URL('delete',args=arglist,vars=vars),
_class='icon delete',
_rel="tooltip",
**{'_data-placement':'right',
'_data-original-title':T('Delete this file (you will be asked to confirm deletion)')})
}} }}
{{block sectionclass}}design{{end}} {{block sectionclass}}design{{end}}
@@ -74,64 +66,66 @@ def deletefile(arglist, vars={}):
<h2>{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"</h2> <h2>{{=T("Edit application")}} "{{=A(app,_href=URL(app,'default','index'),_target="_blank")}}"</h2>
<!-- COLLAPSE/JUMP-TO BUTTONS --> <!-- COLLAPSE/JUMP-TO BUTTONS -->
<div class="right-full controls"> <div class="container">
<p class="buttons-row"> <div class="fill">
{{=searchbox('search')}} <input placeholder="filter" id="search" style="left:100px"/>
<a class="button special btn btn-inverse" href="#" onclick="jQuery('h3>span').click();return false"><span>{{=T("collapse/expand all")}}</span></a> </div>
<span class="buttongroup"> <div class="fill">
{{=button('#models', T("models"))}} <div class="padded">
{{=button('#controllers', T("controllers"))}} <a class="btn rounded small black" href="#" onclick="jQuery('.accordion>[type=checkbox]').click();return false">{{=T("collapse/expand all")}}</a>
{{=button('#views', T("views"))}} <a href="#models" class="btn small rounded orange">{{=T("models")}}</a>
{{=button('#languages', T("languages"))}} <a href="#controllers" class="btn small rounded orange">{{=T("controllers")}}</a>
{{=button('#static', T("static"))}} <a href="#views" class="btn small rounded orange">{{=T("views")}}</a>
{{=button('#modules', T("modules"))}} <a href="#models" class="btn small rounded orange">{{=T("languages")}}</a>
{{=button('#private', T("private files"))}} <a href="#static" class="btn small rounded orange">{{=T("static")}}</a>
{{=button('#plugins', T("plugins"))}} <a href="#models" class="btn small rounded orange">{{=T("modules")}}</a>
</span> <a href="#private" class="btn small rounded orange">{{=T("private files")}}</a>
</p> <a href="#plugins" class="btn small rounded orange">{{=T("plugins")}}</a>
</div>
</div>
</div> </div>
<!-- MODELS --> <!-- MODELS -->
<h3 id="_models" rel="pagebookmark"> <h5 id="_models">
<span class="component" onclick="collapse('models_inner');">{{=T("Models")}}</span> <label class="component" for="models_inner" data-tooltip="{{=T('The data representation, define database tables and sets')}}">{{=T("Models")}}</label>
<a href="#models" rel="tooltip" data-placement="right" data-original-title="{{=T('The data representation, define database tables and sets')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("The data representation, define database tables and sets")}}</span> <input type="checkbox" id="models_inner" checked>
</a><span id="models" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="models_inner" class="component_contents">
{{if not models:}}<p><strong>{{=T("There are no models")}}</strong></p>{{else:}} {{if not models:}}<p><strong>{{=T("There are no models")}}</strong></p>{{else:}}
<div class="controls comptools"> <div class="controls comptools">
{{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}} {{=button(URL(a=app,c='appadmin',f='index'), T('database administration'))}}
{{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}} {{if os.access(os.path.join(request.folder,'..',app,'databases','sql.log'),os.R_OK):}}
{{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}} {{=button(URL('peek/%s/databases/sql.log'%app), 'sql.log')}}
{{pass}} {{pass}}
{{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}} {{=button(URL(a=app, c='appadmin',f='graph_model'), T('graph model'))}}
</div> </div>
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for m in models:}} {{for m in models:}}
{{id="models__"+m.replace('.','__')}} {{id="models__"+m.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span> <li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls"> <span class="filetools controls">
{{=editfile('models',m, dict(id=id))}} {{=editfile('models',m, dict(id=id))}}
{{=deletefile([app, 'models', m], dict(id=id, id2='models'))}} {{=deletefile([app, 'models', m], dict(id=id, id2='models'))}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('models',m, dict(id=id))}} {{=peekfile('models',m, dict(id=id))}}
</span> </span>
<span class="extras"> <span class="extras">
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}} {{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
</span> </span>
</li> </li>
{{pass}} {{pass}}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form1').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button> <button onclick="jQuery('#form1').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div id="form1" class="row-fluid" style="display:none"> <div id="form1" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/models/' % app, 'models')}}</div> <div class="span3">{{=file_create_form('%s/models/' % app, 'models')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- FIND CONTROLLER FUNCTIONS --> <!-- FIND CONTROLLER FUNCTIONS -->
@@ -141,163 +135,162 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
}} }}
<!-- CONTROLLERS --> <!-- CONTROLLERS -->
<h3 id="_controllers" rel="pagebookmark"> <h5 id="_controllers">
<span class="component" onclick="collapse('controllers_inner');">{{=T("Controllers")}}</span> <label class="component" for="controllers_inner" data-tooltip="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}">{{=T("Controllers")}}</label>
<a href="#controllers" rel="tooltip" data-placement="right" data-original-title="{{=T('The application logic, each URL path is mapped in one exposed function in the controller')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("The application logic, each URL path is mapped in one exposed function in the controller")}}</span> <input type="checkbox" id="controllers_inner" checked>
</a><span id="controllers" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="controllers_inner" class="component_contents">
{{if not controllers:}}<p><strong>{{=T("There are no controllers")}}</strong></p>{{else:}} {{if not controllers:}}<p><strong>{{=T("There are no controllers")}}</strong></p>{{else:}}
<div class="controls comptools"> <div class="controls comptools">
{{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}} {{=button(URL(r=request,c='shell',f='index',args=app), T("shell"))}}
{{=button(URL('test',args=app), T("test"))}} {{=button(URL('test',args=app), T("test"))}}
{{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}} {{=button(URL('edit',args=[app,'cron','crontab']), T("crontab"))}}
</div> </div>
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for c in controllers:}} {{for c in controllers:}}
{{id="controllers__"+c.replace('.','__')}} {{id="controllers__"+c.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span> <li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls"> <span class="filetools controls">
{{=editfile('controllers',c, dict(id=id))}} {{=editfile('controllers',c, dict(id=id))}}
{{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}} {{=deletefile([app, 'controllers', c], dict(id=id, id2='controllers'))}}
{{=testfile('controllers',c)}} {{=testfile('controllers',c)}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('controllers',c, dict(id=id))}} {{=peekfile('controllers',c, dict(id=id))}}
</span> </span>
<span class="extras celled"> <span class="extras celled">
{{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}} {{if functions[c]:}}{{=T("exposes")}}{{pass}} {{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
</span> </span>
</li> </li>
{{pass}} {{pass}}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form2').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button> <button onclick="jQuery('#form2').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div id="form2" class="row-fluid" style="display:none"> <div id="form2" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/controllers/' % app, 'controllers')}}</div> <div class="span3">{{=file_create_form('%s/controllers/' % app, 'controllers')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- VIEWS --> <!-- VIEWS -->
<h3 id="_views" rel="pagebookmark"> <h5 id="_views">
<span class="component" onclick="collapse('views_inner');">{{=T("Views")}}</span> <label class="component" for="views_inner" data-tooltip="{{=T('The presentations layer, views are also known as templates')}}">{{=T("Views")}}</label>
<a href="#views" rel="tooltip" data-placement="right" data-original-title="{{=T('The presentations layer, views are also known as templates')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("The presentations layer, views are also known as templates")}}</span> <input type="checkbox" id="views_inner" checked>
</a><span id="views" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="views_inner" class="component_contents"> {{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
{{if not views:}}<p><strong>{{=T("There are no views")}}</strong></p>{{else:}}
<div class="controls comptools"> <div class="controls comptools">
{{=button(LAYOUTS_APP, T("Download layouts from repository"))}} {{=button(LAYOUTS_APP, T("Download layouts from repository"))}}
</div> </div>
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for c in views:}} {{for c in views:}}
{{id="views__"+c.replace('/','__').replace('.','__')}} {{id="views__"+c.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span> <li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetools controls"> <span class="filetools controls">
{{=editfile('views',c, dict(id=id))}} {{=editfile('views',c, dict(id=id))}}
{{=deletefile([app, 'views', c], dict(id=id, id2='views'))}} {{=deletefile([app, 'views', c], dict(id=id, id2='views'))}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('views',c, dict(id=id))}} {{=peekfile('views',c, dict(id=id))}}
</span> </span>
<span class="extras celled celled-one"> <span class="extras celled celled-one">
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}} {{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}} {{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span> </span>
</li> </li>
{{pass}} {{pass}}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form3').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button> <button onclick="jQuery('#form3').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div id="form3" class="row-fluid" style="display:none"> <div id="form3" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/views/' % app, 'views')}}</div> <div class="span3">{{=file_create_form('%s/views/' % app, 'views')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- LANGUAGES --> <!-- LANGUAGES -->
<h3 id="_languages" rel="pagebookmark"> <h5 id="_languages">
<span class="component" onclick="collapse('languages_inner');">{{=T("Languages")}}</span> <label class="component" for="languages_inner" data-tooltip="{{=T('Translation strings for the application')}}">{{=T("Languages")}}</label>
<a href="#languages" rel="tooltip" data-placement="right" data-original-title="{{=T('Translation strings for the application')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("Translation strings for the application")}}</span> <input type="checkbox" id="languages_inner" checked>
</a><span id="languages" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="languages_inner" class="component_contents">
{{if not languages:}}<p><strong>{{=T("There are no translators, only default language is supported")}}</strong></p>{{else:}} {{if not languages:}}<p><strong>{{=T("There are no translators, only default language is supported")}}</strong></p>{{else:}}
<div class="controls comptools"> <div class="controls comptools">
{{=button(URL('update_languages/'+app), T('update all languages'))}} {{=button(URL('update_languages/'+app), T('update all languages'))}}
</div> </div>
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for lang in sorted(languages): {{for lang in sorted(languages):
file = lang+'.py' file = lang+'.py'
id = "languages__"+file.replace('.','__')}} id = "languages__"+file.replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark" class="li-row"><span id="{{=id}}" class="hashstick">&nbsp;</span> <li id="{{='_'+id}}" class="li-row"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="li-controls"> <span class="li-controls">
<span class="filetools controls"> <span class="filetools controls">
{{=editlanguagefile('languages',file)}} {{=editlanguagefile('languages',file)}}
{{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}} {{=deletefile([app, 'languages', file], dict(id=id, id2='languages'))}}
</span> </span>
<span class=""> <span class="">
{{=peekfile('languages',file, dict(id=id))}} {{=peekfile('languages',file, dict(id=id))}}
</span> </span>
</span> <!-- /li-row --> </span> <!-- /li-row -->
<span class="extras celled"> <span class="extras celled">
( (
{{=T("Plural-Forms:")}} {{=T("Plural-Forms:")}}
{{p=languages[lang][3:7]}} {{p=languages[lang][3:7]}}
{{if p[2] == 'default':}} {{if p[2] == 'default':}}
<span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}} <span class='error text-error'>{{=T("rules are not defined")}}</span> {{=T.M("(file **gluon/contrib/plural_rules/%s.py** is not found)",lang[:2])}}
{{else:}} {{else:}}
{{if p[3] == 1:}} {{if p[3] == 1:}}
{{=B(T("are not used"))}} {{=B(T("are not used"))}}
{{else:}} {{else:}}
{{pfile=p[0]}} {{pfile=p[0]}}
{{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;"> {{if p[1]!=0:}}<span style="display:inline-block;margin-top:-10px;">
<span class="filetools controls"> <span class="filetools controls">
{{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}} {{=editpluralsfile('languages',pfile,dict(nplurals=p[3]))}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('languages',pfile,dict(id=id))}} {{=peekfile('languages',pfile,dict(id=id))}}
</span></span> </span></span>
{{else:}} {{else:}}
<b>{{=T("are not used yet")}}</b> <b>{{=T("are not used yet")}}</b>
{{pass}}
{{pass}}
{{pass}}
)
</span>
</li>
{{pass}} {{pass}}
{{pass}}
{{pass}}
)
</span>
</li>
{{pass}}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form4').slideToggle()" class="btn btn-mini">{{=T('Create')}}</button> <button onclick="jQuery('#form4').slideToggle()" class="btn rounded small">{{=T('Create')}}</button>
<div id="form4" class="row-fluid" style="display:none"> <div id="form4" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/languages/' % app, 'languages', T('(something like "it-it")'))}}</div> <div class="span3">{{=file_create_form('%s/languages/' % app, 'languages', T('(something like "it-it")'))}}</div>
</div>
</div> </div>
</div>
</div>
</div> </div>
<!-- STATIC --> <!-- STATIC -->
<h3 id="_static" rel="pagebookmark"> <h5 id="_static">
<span class="component" onclick="collapse('static_inner');">{{=T("Static")}}</span> <label class="component" for="static_inner" data-tooltip="{{=T('These files are served without processing, your images go here')}}">{{=T("Static")}}</label>
<a href="#static" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are served without processing, your images go here')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("These files are served without processing, your images go here")}}</span> <input type="checkbox" id="static_inner" checked>
</a><span id="static" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="static_inner" class="component_contents">
{{if not statics:}}<p><strong>{{=T("There are no static files")}}</strong></p>{{else:}} {{if not statics:}}<p><strong>{{=T("There are no static files")}}</strong></p>{{else:}}
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{ {{
path=[] path=[]
for file in statics+['']: for file in statics+['']:
items=file.split('/') items=file.split('/')
@@ -308,88 +301,89 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
path.append(file_path[len(path)]) path.append(file_path[len(path)])
thispath = regex_space.sub('-', 'static__'+'__'.join(path)) thispath = regex_space.sub('-', 'static__'+'__'.join(path))
}} }}
<li class="folder"><i>&nbsp;</i> <li class="folder"><i>&nbsp;</i>
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a> <a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{ <ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else: else:
path = path[:-1] path = path[:-1]
}} }}
</ul></li> </ul>
</li>
{{ {{
pass pass
pass pass
if filename: if filename:
}} }}
<li> <li>
<span class="filetools controls"> <span class="filetools controls">
{{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}} {{=editfile('static',file, dict(id="static"))}} {{=deletefile([app,'static',file], dict(id="static",id2="static"))}}
</span> </span>
<span class="file"> <span class="file">
<a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a> <a href="{{=URL(a=app,c='static',f=file)}}">{{=filename}}</a>
</span> </span>
</li>{{ </li>{{
pass pass
pass pass
}} }}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form5').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button> <button onclick="jQuery('#form5').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div id="form5" class="row-fluid" style="display:none"> <div id="form5" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/static/' % app, 'static')}}<em>{{=T('or alternatively')}}</em></div> <div class="span3">{{=file_create_form('%s/static/' % app, 'static')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/static/' % app, 'static')}}</div> <div class="span3">{{=file_upload_form('%s/static/' % app, 'static')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- MODULES --> <!-- MODULES -->
<h3 id="_modules" rel="pagebookmark"> <h5 id="_modules">
<span class="component" onclick="collapse('modules_inner');">{{=T("Modules")}}</span> <label class="component" for="modules_inner" data-tooltip="{{=T('Additional code for your application')}}">{{=T("Modules")}}</label>
<a href="#modules" rel="tooltip" data-placement="right" data-original-title="{{=T('Additional code for your application')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("Additional code for your application")}}</span> <input type="checkbox" id="modules_inner" checked>
</a><span id="modules" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="modules_inner" class="component_contents">
{{if not modules:}}<p><strong>{{=T("There are no modules")}}</strong></p>{{else:}} {{if not modules:}}<p><strong>{{=T("There are no modules")}}</strong></p>{{else:}}
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for m in modules:}} {{for m in modules:}}
{{id="modules__"+m.replace('/','__').replace('.','__')}} {{id="modules__"+m.replace('/','__').replace('.','__')}}
<li id="{{='_'+id}}" rel="pagebookmark"><span id="{{=id}}" class="hashstick">&nbsp;</span> <li id="{{='_'+id}}"><span id="{{=id}}" class="hashstick">&nbsp;</span>
<span class="filetols controls"> <span>
{{=editfile('modules',m,dict(id=id))}} {{=editfile('modules',m,dict(id=id))}}
{{if m!='__init__.py':}} {{if m!='__init__.py':}}
{{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}} {{=deletefile([app, 'modules', m], dict(id=id, id2='modules'))}}
{{pass}} {{pass}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('modules',m, dict(id=id))}} {{=peekfile('modules',m, dict(id=id))}}
</span> </span>
</li> </li>
{{pass}} {{pass}}
</ul> </ul>
{{pass}} {{pass}}
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form6').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button> <button onclick="jQuery('#form6').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div id="form6" class="row-fluid" style="display:none"> <div id="form6" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div> <div class="span3">{{=file_create_form('%s/modules/' % app, 'modules')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div> <div class="span3">{{=file_upload_form('%s/modules/' % app, 'modules')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- PRIVATE --> <!-- PRIVATE -->
<h3 id="_private" rel="pagebookmark"> <h5 id="_private">
<span class="component" onclick="collapse('private_inner');">{{=T("Private files")}}</span> <label class="component" for="private_inner" data-tooltip="{{=T('These files are not served, they are only available from within your app')}}">{{=T("Private files")}}</label>
<a href="#private" rel="tooltip" data-placement="right" data-original-title="{{=T('These files are not served, they are only available from within your app')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("These files are not served, they are only available from within your app")}}</span> <input type="checkbox" id="private_inner" checked>
</a><span id="private" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="private_inner" class="component_contents">
{{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{else:}} {{if not privates:}}<p><strong>{{=T("There are no private files")}}</strong></p>{{else:}}
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{ {{
path=[] path=[]
for file in privates+['']: for file in privates+['']:
items=file.split('/') items=file.split('/')
@@ -400,72 +394,74 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
path.append(file_path[len(path)]) path.append(file_path[len(path)])
thispath='private__'+'__'.join(path) thispath='private__'+'__'.join(path)
}} }}
<li class="folder"> <li class="folder">
<a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a> <a href="javascript:collapse('{{=thispath}}');" class="file">{{=path[-1]}}/</a>
<ul id="{{=thispath}}" style="display: none;" class="sublist">{{ <ul id="{{=thispath}}" style="display: none;" class="sublist">{{
else: else:
path = path[:-1] path = path[:-1]
}} }}
</ul> </ul>
</li> </li>
{{ {{
pass pass
pass pass
if filename: if filename:
}} }}
<li> <li>
<span class="filetools controls"> <span class="filetools controls">
{{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}} {{=editfile('private',file, dict(id="private"))}} {{=deletefile([app,'private',file], dict(id="private",id2="private"))}}
</span> </span>
<span class="file"> <span class="file">
{{=peekfile('private',file, dict(id="private"))}} {{=peekfile('private',file, dict(id="private"))}}
</span> </span>
</li>{{ </li>{{
pass pass
pass pass
}} }}
{{pass}} {{pass}}
</ul> </ul>
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form7').slideToggle()" class="btn btn-mini">{{=T('Create/Upload')}}</button> <button onclick="jQuery('#form7').slideToggle()" class="btn rounded small">{{=T('Create/Upload')}}</button>
<div id="form7" class="row-fluid" style="display:none"> <div id="form7" class="row-fluid" style="display:none">
<div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div> <div class="span3">{{=file_create_form('%s/private/' % app, 'private')}}<em>{{=T('or alternatively')}}</em></div>
<div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div> <div class="span3">{{=file_upload_form('%s/private/' % app, 'private')}}</div>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- PLUGINS --> <!-- PLUGINS -->
<h3 id="_plugins" rel="pagebookmark"> <h5 id="_plugins">
<span class="component" onclick="collapse('plugins_inner');">{{=T("Plugins")}}</span> <label class="component" for="plugins_inner" data-tooltip="{{=T('To create a plugin, name a file/folder plugin_[name]')}}">{{=T("Plugins files")}}</label>
<a href="#plugins" rel="tooltip" data-placement="right" data-original-title="{{=T('To create a plugin, name a file/folder plugin_[name]')}}"> </h5>
{{=helpicon()}} <div class="accordion">
<span>{{=T("To create a plugin, name a file/folder plugin_[name]")}}</span> <input type="checkbox" id="plugins_inner" checked>
</a><span id="plugins" class="hashstick">&nbsp;</span><a href="#" class="tophashlink btn btn-mini btn-warning"><span>top</span></a> <div>
</h3> <a href="#" class="right btn rounded small yellow">top</a>
<div id="plugins_inner" class="component_contents">
{{if plugins:}} {{if plugins:}}
<ul class="unstyled act_edit"> <ul class="unstyled act_edit">
{{for plugin in plugins:}} {{for plugin in plugins:}}
{{id="plugins__"+plugin.replace('/','__').replace('.','__')}} {{id="plugins__"+plugin.replace('/','__').replace('.','__')}}
<li id="{{=id}}"> <li id="{{=id}}">
{{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}} {{=A('plugin_%s' % plugin, _class='file', _href=URL('plugin', args=[app, plugin], vars=dict(id=id, id2='plugins')))}}
</li> </li>
{{pass}} {{pass}}
</ul> </ul>
{{else:}} {{else:}}
<p><strong>{{=T('There are no plugins')}}</strong></p> <p><strong>{{=T('There are no plugins')}}</strong></p>
{{pass}} {{pass}}
<div class="controls comptools"> <div class="controls comptools">
{{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}} {{=button(URL(c="default", f="plugins", args=[app,]), T('Download plugins from repository'))}}
</div> </div>
<div class="controls formfield"> <div class="silver rounded padded">
<button onclick="jQuery('#form8').slideToggle()" class="btn btn-mini">{{=T('Upload')}}</button> <button onclick="jQuery('#form8').slideToggle()" class="btn rounded small">{{=T('Upload')}}</button>
<div id="form8" class="row-fluid" style="display:none"> <div id="form8" class="row-fluid" style="display:none">
<div class="row-fluid"> <div class="row-fluid">
<div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div> <div class="span3">{{=upload_plugin_form(app, 'plugins')}}</div>
</div> </div>
</div> </div>
</div>
</div>
</div> </div>
<script> <script>
@@ -477,11 +473,11 @@ function filter_files() {
message=data['message']; message=data['message'];
for(var i=0; i<files.length; i++) for(var i=0; i<files.length; i++)
jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown(); jQuery('li#_'+files[i].replace(/\//g,'__').replace('.','__')).slideDown();
jQuery('.flash').html(message).slideDown(); jQuery('.w2p_flash').html(message).slideDown();
}); });
} else { } else {
jQuery('.component_contents li, .formfield, .comptools').slideDown(); jQuery('.component_contents li, .formfield, .comptools').slideDown();
jQuery('.flash').html('').hide(); jQuery('.w2p_flash').html('').hide();
} }
} }
jQuery(document).ready(function(){ jQuery(document).ready(function(){
@@ -490,6 +486,7 @@ jQuery(document).ready(function(){
if(code==13) filter_files(); if(code==13) filter_files();
}); });
jQuery('#search_start').click(function(e){ filter_files(); }); jQuery('#search_start').click(function(e){ filter_files(); });
}); });
</script> </script>
<!-- end "design" block --> <!-- end "design" block -->
+1 -1
View File
@@ -32,7 +32,7 @@ def file_create_form(location, anchor=None, helptext=""):
<!-- begin "edit" block --> <!-- begin "edit" block -->
{{ {{
def shortcut(combo, description): def shortcut(combo, description):
return XML('<li class="span5"><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description)) return XML('<li><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
def listfiles(app, dir, regexp='.*\.py$'): def listfiles(app, dir, regexp='.*\.py$'):
files = sorted( files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp)) listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
+12 -13
View File
@@ -1,23 +1,22 @@
{{extend 'layout.html'}} {{extend 'layout.html'}}
{{block sectionclass}}login{{end}}
<!-- begin "index" block --> <!-- begin "index" block -->
<h2>web2py&trade; {{=T('Web Framework')}}</h2> <h2>web2py&trade; {{=T('Web Framework')}}</h2>
<h3>{{=T('Login to the Administrative Interface')}}</h3> <div class="twothirds padded lifted">
<div class="form row-fluid">
{{if request.is_https or request.is_local:}} {{if request.is_https or request.is_local:}}
<form action="{{=URL(r=request)}}" method="post" class="span4 well"> <form action="{{=URL(r=request)}}" method="post" class="span4 well">
<label for="password">{{=T('Administrator Password:')}}</label> <h5>{{=T('Login to the Administrative Interface')}}</h5>
<input type="password" name="password" id="password"/> <label class="spaced" for="password">{{=T('Administrator Password:')}}</label>
<input type="hidden" name="send" value="{{=send}}"/> <input class="spaced" type="password" name="password" id="password"/>
<div class="controls"><button type="submit" name="login" class="btn">{{=T('Login')}}</button></div> <input class="spaced" type="hidden" name="send" value="{{=send}}"/>
</form> <button class="spaced" type="submit" name="login">{{=T('Login')}}</button>
</form>
{{else:}} {{else:}}
<p class="help span7 alert alert-block alert-warning">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p> <p class="help span7 alert alert-block alert-warning">{{=T('ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.')}}</p>
{{pass}} {{pass}}
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
jQuery(document).ready(function(){ jQuery(document).ready(function(){
jQuery("#password").focus(); jQuery("#password").focus();
}); });
</script> </script>
<!-- end "index" block --> <!-- end "index" block -->
+1 -1
View File
@@ -144,7 +144,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{=peekfile('views',c)}} {{=peekfile('views',c)}}
</span> </span>
<span class="extras celled"> <span class="extras celled">
{{if extend.has_key(c):}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}} {{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}} {{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
</span> </span>
</li> </li>
+86 -81
View File
@@ -1,19 +1,19 @@
{{extend 'layout.html'}} {{extend 'layout.html'}}
{{import os, glob}} {{import os, glob}}
{{block sectionclass}}site{{end}}
<!-- begin "site" block --> <!-- begin "site" block -->
<div class="row-fluid"> <div class="container">
<div class="applist f60 span7"> <div class="twothirds">
<div class="applist_inner"> <div class="padded">
<h2>{{=T("Installed applications")}}</h2> <h2>{{=T("Installed applications")}}</h2>
<table width="100%" class="table"> <table>
{{for a in apps:}} <tbody>
<tr>{{buttons = []}} {{for a in apps:}}
<td> <tr>{{buttons = []}}
<td>
{{if a==request.application:}} {{if a==request.application:}}
<h4 class="currentapp">{{=a}} ({{=T('currently running')}})</h4> <a class="btn rounded gray">{{=a}} ({{=T('currently running')}})</a>
{{else:}} {{else:}}
<h4 class="editableapp">{{=A(a,_href=URL(a,'default','index'))}}</h4> <a class="btn rounded orange" href="{{=URL(a,'default','index')}}">{{=a}}</a>
{{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}} {{if MULTI_USER_MODE and db.app(name=a):}}(created by {{="%(first_name)s %(last_name)s" % db.auth_user[db.app(name=a).owner]}}){{pass}}
{{if not os.path.exists('applications/%s/compiled' % a):}} {{if not os.path.exists('applications/%s/compiled' % a):}}
{{buttons.append((URL('design',args=a), T("Edit")))}} {{buttons.append((URL('design',args=a), T("Edit")))}}
@@ -27,7 +27,9 @@
{{buttons.append((URL('pack',args=a), T("Pack all")))}} {{buttons.append((URL('pack',args=a), T("Pack all")))}}
{{buttons.append((URL('pack_custom',args=a), T("Pack custom")))}} {{buttons.append((URL('pack_custom',args=a), T("Pack custom")))}}
{{if not os.path.exists('applications/%s/compiled' % a):}} {{if not os.path.exists('applications/%s/compiled' % a):}}
{{buttons.append((URL('compile_app',args=a), T("Compile")))}} {{buttons.append((URL('compile_app',args=[a, 'skip_failed_views']),
T("Compile (skip failed views)")))}}
{{buttons.append((URL('compile_app',args=a), T("Compile (all or nothing)")))}}
{{else:}} {{else:}}
{{buttons.append((URL('pack',args=(a, 'compiled')), T("Pack compiled")))}} {{buttons.append((URL('pack',args=(a, 'compiled')), T("Pack compiled")))}}
{{if glob.glob('applications/%s/controllers/*.py' % a):}} {{if glob.glob('applications/%s/controllers/*.py' % a):}}
@@ -41,28 +43,30 @@
{{if a!=request.application:}} {{if a!=request.application:}}
{{buttons.append((URL('uninstall',args=a), T("Uninstall")))}} {{buttons.append((URL('uninstall',args=a), T("Uninstall")))}}
{{pass}} {{pass}}
</td> </td>
<td> <td>
<div class="btn-group"> <ul class="menu">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> <li>
{{=T('Manage')}} <a class="btn white rounded">{{=T('Manage')}}</a>
<span class="caret"></span> <ul>
</a> {{for link,name in buttons:}}
<ul class="dropdown-menu"> {{=LI(A(name,_href=link))}}
{{for link,name in buttons:}} {{pass}}
{{=LI(A(name,_href=link))}} </ul>
</li>
</ul>
</td>
<td>
{{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
</td>
</tr>
{{pass}} {{pass}}
</ul> </tbody>
</div>
{{=button_enable(URL('enable',args=a), a) if a!='admin' else ''}}
</td>
</tr>
{{pass}}
</table> </table>
</div> </div>
</div> <!-- /applist --> </div> <!-- /applist -->
<div class="sidebar fl60 span5"> <div class="third black">
<div class="sidebar_inner controls well well-small"> <div class="padded">
<!-- CHANGE ADMIN PWD --> <!-- CHANGE ADMIN PWD -->
<div class="pwdchange pull-right"> <div class="pwdchange pull-right">
{{if MULTI_USER_MODE:}} {{if MULTI_USER_MODE:}}
@@ -75,76 +79,77 @@
{{if is_manager():}} {{if is_manager():}}
<!-- VERSION --> <!-- VERSION -->
<div class="box"> <div class="box">
<h4>{{=T("Version")}}</h4> <h6>{{=T("Version")}}</h6>
<p> <p>
<tt>{{=myversion}}</tt><br/> <tt>{{=myversion}}</tt><br/>
{{running_on = T("Running on %s", request.env.server_software or 'Unknown')}} {{running_on = T("Running on %s", request.env.server_software or 'Unknown')}}
({{="%s, Python %s" % (running_on, myplatform)}}) ({{="%s, Python %s" % (running_on, myplatform)}})
</p> </p>
<p id="check_version" class="row-buttons"> <p id="check_version" class="row-buttons">
{{if session.check_version:}} {{if session.check_version:}}
{{=T('Checking for upgrades...')}} {{=T('Checking for upgrades...')}}
<script>ajax('{{=URL('check_version')}}',[],'check_version');</script> <script>ajax('{{=URL('check_version')}}',[],'check_version');</script>
{{session.check_version=False}} {{session.check_version=False}}
{{else:}} {{else:}}
{{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}} {{=button("javascript:ajax('"+URL('check_version')+"',[],'check_version')", T('Check for upgrades'))}}
{{pass}} {{pass}}
</p> </p>
{{if session.is_mobile=='auto':}} {{if session.is_mobile=='auto':}}
<p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p> <p>{{=A(T('Try the mobile interface'),_href=URL('plugin_jqmobile','about'))}}</p>
{{pass}} {{pass}}
</div> <!-- /VERSION --> </div> <!-- /VERSION -->
{{pass}} {{pass}}
{{if MULTI_USER_MODE and is_manager():}} {{if MULTI_USER_MODE and is_manager():}}
<!-- MULTI_USER_INTERFACE --> <!-- MULTI_USER_INTERFACE -->
<div class="box"> <div class="box">
<h4>{{=T("Multi User Mode")}}</h4> <h6>{{=T("Multi User Mode")}}</h6>
<p class="row-buttons"> <p class="row-buttons">
{{=button(URL('bulk_register'),T('Bulk Register'))}} {{=button(URL('bulk_register'),T('Bulk Register'))}}
{{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}} {{=button(URL('manage_students',vars={'order':'auth_user.id'}),T('Manage Students'))}}
</p> </p>
</div> <!-- /MULTI_USER_INTERFACE --> </div> <!-- /MULTI_USER_INTERFACE -->
{{pass}} {{pass}}
<!-- SCAFFOLD APP --> <!-- SCAFFOLD APP -->
<div class="box"> <div class="box">
<h4>{{=T("New simple application")}}</h4> <h6>{{=T("New simple application")}}</h6>
{{=form_create.custom.begin}} {{=form_create.custom.begin}}
{{=LABEL(T("Application name:"))}} {{=LABEL(T("Application name:"))}}
{{=form_create.custom.widget.name}} {{=form_create.custom.widget.name}}
<div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div> <div class="controls"><button type="submit" class="btn">{{=T('Create')}}</button></div>
{{=form_create.custom.end}} {{=form_create.custom.end}}
</div> <!-- /SCAFFOLD APP --> </div> <!-- /SCAFFOLD APP -->
<!-- UPLOAD PACKAGE --> <!-- UPLOAD PACKAGE -->
<div class="box"> <div class="box">
<h4>{{=T("Upload and install packed application")}}</h4> <h6>{{=T("Upload and install packed application")}}</h6>
{{=form_update.custom.begin}} {{=form_update.custom.begin}}
<label for="appupdate_name">{{=T("Application name:")}}</label> <label for="appupdate_name">{{=T("Application name:")}}</label>
{{=form_update.custom.widget.name}} {{=form_update.custom.widget.name}}
<label for="appupdate_file">{{=T("Upload a package:")}}</label> <label for="appupdate_file">{{=T("Upload a package:")}}</label>
{{=form_update.custom.widget.file}} {{=form_update.custom.widget.file}}
<label for="appupdate_url">{{=T("Or Get from URL:")}}</label> <label for="appupdate_url">{{=T("Or Get from URL:")}}</label>
{{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small> {{=form_update.custom.widget.url}}<small class="help-block">({{=T('can be a git repo')}})</small>
<div class="controls"> <div class="controls">
<label class="checkbox"> <label class="checkbox">
{{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}} {{=form_update.custom.widget.overwrite}} {{=T("Overwrite installed app")}}
</label> </label>
<button type="submit" class='btn'>{{=T('Install')}}</button> <button type="submit" class='btn'>{{=T('Install')}}</button>
</div> </div>
{{=form_update.custom.end}} {{=form_update.custom.end}}
</div> <!-- /UPLOAD PACKAGE --> </div> <!-- /UPLOAD PACKAGE -->
<!-- DEPLOY ON GAE --> <!-- DEPLOY ON GAE -->
<div class="box"> <div class="box">
<h4>{{=T("Deploy")}}</h4> <h6>{{=T("Deploy")}}</h6>
<p class="row-buttons"> <p class="row-buttons">
{{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}} {{=button(URL('gae','deploy'), T('Deploy on Google App Engine'))}}
{{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}} {{=button(URL('openshift','deploy'),T('Deploy to OpenShift'))}}
</p> {{=button(URL('pythonanywhere','deploy'), T('Deploy to PythonAnywhere'))}}
</p>
</div> <!-- /DEPLOY ON GAE --> </div> <!-- /DEPLOY ON GAE -->
<!-- APP WIZARD --> <!-- APP WIZARD -->
<div class="box"> <div class="box">
<h4>{{=T("New application wizard")}}</h4> <h6>{{=T("New application wizard")}}</h6>
<p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/> <p>{{=button(URL('wizard','index'), T('Start wizard'))}}<br/>
{{=T("(requires internet access, experimental)")}}</p> {{=T("(requires internet access, experimental)")}}</p>
</div> <!-- /APP WIZARD --> </div> <!-- /APP WIZARD -->
<!-- TWITTER TIMELINE --> <!-- TWITTER TIMELINE -->
<div class="box twitter-timeline"> <div class="box twitter-timeline">
+67 -111
View File
@@ -1,113 +1,69 @@
<!DOCTYPE html> <html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>
<meta charset="utf-8">
<head> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="content-type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="P3P" content="CP=\"IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA\"" /> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<title>{{=response.title or URL()}}</title> <link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
{{ <link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
response.files.append(URL('static','css/bootstrap.min.css')) <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
response.files.append(URL('static','css/bootstrap_essentials.css')) <style>
response.files.append(URL('static','css/bootstrap-responsive.min.css')) th, td {color: black}
}} tbody tr:hover {background-color:transparent}
{{include 'web2py_ajax.html'}} tbody tr {border-bottom: none}
</head> p {text-align: left}
pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
<body class="{{=T('direction: ltr') == 'direction: rtl' and 'RTLbody' or ''}} {{block sectionclass}}home{{end}}"> a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
[type=submit], [type=button] {border-radius:5px!important;padding:5px 10px;margin-top:10px}
<!-- NAVBAR </style>
============== --> {{
<div id="header" class="navbar navbar-inverse navbar-fixed-top"> left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
<div class="navbar-inner"> right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
<div class="container-fluid"> middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse"> (left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
<span class="icon-bar"></span> }}
<span class="icon-bar"></span> {{include "web2py_ajax.html"}}
<span class="icon-bar"></span> </head>
</button> <body class="black">
<div id="start" class="brand_wrapper"> <header class="black padded">
<a href="{{=URL('default', 'index')}}" class="button brand" ><span>web2py&trade; {{=T('administrative interface')}}</span></a> <div class="container middle max900">
</div> <div class="fill middle">
<div class="nav-collapse"> <label class="ham padded fa fa-bars" for="menu"></label>
{{if response.menu is not None:}} <div class="burger accordion">
<ul id="menu" class="nav pull-right"> <input type="checkbox" id="menu"/>
{{for _name,_active,_link in response.menu:}} {{=MENU(response.menu,_class='menu')}}
<li>{{=A(SPAN(_name), _href=_link, _class=_active and 'button select' or 'button')}}</li> </div>
{{pass}} </div>
</ul> </div>
{{pass}} </header>
</div><!--/.nav-collapse --> {{if response.flash:}}
</div><!-- /container-fluid --> <div class="w2p_flash">
</div><!-- /navbar-inner --> {{=response.flash}}
</div><!-- /#header --> </div>
{{pass}}
<!-- MAIN <main class="white">
=========== --> <div class="hidden">{{block sectionclass}}design{{end}}</div>
<div id="{{=globals().get('main_id', 'main')}}" class="container-fluid"> <div class="container max900">
<div id="main_inner" class="row-fluid"> {{if left_sidebar_enabled:}}
<div class="span12"> <div class="quarter padded">{{block left_sidebar}}{{end}}</div>
<div class="flash alert">{{=response.flash or ''}}</div>
{{include}}
</div><!-- /main span12 -->
</div><!-- /main row-fluid -->
</div><!-- /#main -->
<!-- FOOTER
============== -->
{{block footer}}
<footer id="footer" class="fixed">
<p><span>{{=T('Powered by')}} {{=A('web2py', _href='http://www.web2py.com')}}&trade; {{=T('created by')}} Massimo Di Pierro &copy;2007-{{=request.now.year}}
{{if hasattr(T,'get_possible_languages_info'):}}
- {{=T('Admin language')}}</span>
<select name="adminlanguage" onchange="var date = new Date();cookieDate=date.setTime(date.getTime()+(100*24*60*60*1000));document.cookie='adminLanguage='+this.options[this.selectedIndex].id+'; expires='+cookieDate+'; path=/';window.location.reload()">
{{for langinfo in sorted([(code,info[1]) for code,info in T.get_possible_languages_info().iteritems() if code != 'default']):}}
<option {{=T.accepted_language==langinfo[0] and 'selected' or ''}} {{='id='+langinfo[0]}} >{{=langinfo[1]}}</option>
{{pass}}
</select>
{{else:}}
</span>{{pass}}
</p>
</footer><!-- /#footer -->
{{end}}
<!-- BS JAVASCRIPT
====================== -->
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script>
<script type="text/javascript">
jQuery(document).ready(function(){
jQuery("[rel=tooltip]").tooltip();
});
</script>
<script>
// ====================
// upload input mask
// ====================
function FileSelectHandler(e) {
e.stopPropagation();
var filename = e.target.value.split(/\\|\//).pop();
jQuery('#fileselect>span').removeClass('txtPlaceholder').text(filename)
}
jQuery(document).ready(function(){
var iupload = jQuery('#appupdate_file');
var ow = 300, oh = 20;
var iplaceholder = jQuery('<span class="txtPlaceholder">{{=T("no package selected")}}</span>'),
iuploadbtn = jQuery('<button class="btn btn-inverse btn-mini uploadbtn"><i class="icon-white icon-circle-arrow-up"></i></button>');
iupload
.addClass('masked')
.wrap('<div id="fileselect" style="width:'+ow+'px;height:'+oh+'px"></div>')
.on('change', function(event){FileSelectHandler(event)});
jQuery('#fileselect').append(iplaceholder, iuploadbtn);
});
</script>
{{if request.function in ('index','site'):}}
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank">
<!-- http://webchat.freenode.net/?channels=web2py" //-->
<img src="{{=URL('static','images/questions.png')}}" />
</a>
{{pass}} {{pass}}
</body> <div class="{{=middle_column}} padded">{{include}}</div>
{{if right_sidebar_enabled:}}
<div class="quarter padded">{{block right_sidebar}}{{end}}</div>
{{pass}}
</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> </html>
@@ -0,0 +1,176 @@
{{extend 'layout.html'}}
<h2><span style="color:#139FD7">python</span>anywhere {{=T('Deployment Interface')}}</h2>
<div id="register_form">
<h3>{{=T('Login/Register')}}</h3>
<form class="form-horizontal" id="palogin">
<div class="control-group" id="username__row">
<label class="control-label" for="username">{{=T('Username')}}</label>
<div class="controls">
<input type="text" name="username" id="username"><span class="help-inline">*</span>
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="email_address__row">
<label class="control-label" for="email_address">{{=T('Email Address')}}</label>
<div class="controls">
<input type="text" name="email_address" id="email_address">
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="pythonanywhere_password__row">
<label class="control-label" for="pythonanywhere_password">{{=T('PythonAnywhere Password')}}</label>
<div class="controls">
<input type="password" name="pythonanywhere_password" id="pythonanywhere_password">
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="web2py_admin_password__row">
<label class="control-label" for="web2py_admin_password">{{=T('web2py Admin Password')}}</label>
<div class="controls">
<input type="password" name="web2py_admin_password" id="web2py_admin_password"><span class="help-inline">*</span>
<span class="help-block"></span>
</div>
</div>
<div class="control-group" id="accepts_terms__row">
<div class="controls">
<label class="checkbox">
<input type="checkbox" name="accepts_terms" id="accepts_terms"><a target="_blank" href="https://www.pythonanywhere.com/terms/">{{=T('Accept Terms')}}</a>
</label>
<span class="help-block"></span>
</div>
</div>
<div class="control-group">
<div class="controls">
<button type="submit" class="btn btn-primary" id="submit_palogin">{{=T('Submit')}}</button>
</div>
</div>
</form>
<p>* {{=T('You only need these if you have already registered')}}</p>
</div>
<div class="row-fluid" id="app_manager" style="display:none;">
<div class="span6">
<h3>{{=T('Local Apps')}}</h3>
<form id="apppicker">
<select name="apps" class="form-control" id="local" multiple>
<option>{{=T('Loading...')}}</option>
</select>
<input type="submit" value="Deploy" id="deploy_button" class="btn btn-primary">
</form>
<div class="alert alert-info">
<strong>{{=T('Warning!')}}</strong> {{=T('if your application uses a database other than sqlite you will then have to configure its DAL in pythonanywhere.')}}
</div>
</div>
<div class="span6">
<h3>{{=T('PythonAnywhere Apps')}}</h3>
<ul id="pythonanywhere">
<li>{{=T('Loading...')}}</li>
</ul>
</div>
</div>
<script>
$(document).ready(function() {
$('#palogin').off('submit');
$('#palogin').submit(function(event) {
var data = $('#palogin').serialize();
$.web2py.disableElement($('#submit_palogin'));
$.web2py.disableFormElements($('#palogin'));
$.ajax({
url: '{{=URL("pythonanywhere", "create_account")}}',
type: 'POST',
data: data,
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
$('#palogin .error').removeClass('error');
$('#palogin .help-block').text('');
if(data.status == 'error') {
for(var error in data.errors) {
$('#' + error + '__row').addClass('error');
$('#' + error + '__row .help-block').text(data.errors[error][0]);
}
$.web2py.enableElement($('#submit_palogin'));
$.web2py.enableFormElements($('#palogin'));
$.web2py.flash("{{=T('Form has errors')}}");
} else {
$.web2py.flash("{{=T('Login successful')}}");
$('#register_form').hide();
$('#app_manager').show();
refresh_apps();
}
}).fail(function(){
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
$.web2py.enableElement($('#submit_palogin'));
$.web2py.enableFormElements($('#palogin'));
});
event.preventDefault();
});
$('#apppicker').off('submit');
$('#apppicker').submit(function(event) {
var data = $('#apppicker').serialize();
$.web2py.disableElement($('#deploy_button'));
$.ajax({
url: '{{=URL("pythonanywhere", "bulk_install")}}',
type: 'POST',
data: {username: $('#username').val(), password: $('#web2py_admin_password').val(), apps: $('#local').val()},
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
refresh_apps();
$.web2py.enableElement($('#deploy_button'));
}).fail(function(){
$.web2py.flash("{{=T('Something went wrong please wait a few minutes before retrying')}}");
$.web2py.enableElement($('#deploy_button'));
});
event.preventDefault();
});
});
function refresh_apps() {
// Refresh List of Apps
$('#deploy_button').prop('disabled', true);
$.ajax({
url: '{{=URL("pythonanywhere", "list_apps")}}',
type: 'GET',
data: {username: $('#username').val(), password: $('#web2py_admin_password').val()},
dataType: 'json',
}).done(function(data, textStatus, jqXHR) {
var i = 0;
$('#local').html('')
for(i = 0; i < data.local.length; i++) {
$('#local').append($('<option>', {
value: data.local[i],
text: data.local[i]
}));
}
$('#local').multiSelect('refresh');
$('#pythonanywhere').html('')
for(i = 0; i < data.pythonanywhere.length; i++) {
$('#pythonanywhere').append($('<li>', {
text: data.pythonanywhere[i]
}));
}
$('#deploy_button').prop('disabled', false);
$.web2py.hide_flash();
}).fail(function(){
// Mostly this happens if it's a new account, just waiting a bit should be enough.
$.get('http://' + $('#username').val() + '.pythonanywhere.com'); // Kickstart the instance
$.web2py.flash("{{=T('Please wait, giving pythonanywhere a moment...')}}");
setTimeout(refresh_apps, 30000);
});
}
</script>
@@ -576,7 +576,7 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC') meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
+1 -1
View File
@@ -26,7 +26,7 @@ def what():
return response.render(images=images) 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(): def download():
return response.render() return response.render()
@@ -9,6 +9,7 @@
#### Learning and Demos #### Learning and Demos
- [[Intro video http://www.youtube.com/watch?v=BXzqmHx6edY]] and [[code examples https://github.com/mjhea0/web2py]] - [[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]] - [[Step by step tutorial https://milesm.pythonanywhere.com/wiki]]
- [[web2py Reference Project http://www.web2pyref.com/]]
- [[Killer Web Development Tutorial http://killer-web-development.com/]] - [[Killer Web Development Tutorial http://killer-web-development.com/]]
- [[Real Python for the Web http://www.realpython.com]] (web development with web2py and more!) - [[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) - [[Admin Demo http://www.web2py.com/demo_admin popup]] (web-based IDE)
@@ -24,6 +24,10 @@ French speakers group
``web2py-fr``:groupdates ``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 Group
Japanese speakers 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; .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}
border-radius: 10px; .calendar table {border-collapse:collapse}
-moz-border-radius: 10px; .calendar tbody tr:hover {background-color:#fbf6d9}
-webkit-border-radius: 10px; .calendar td, th {padding:5px; vertical-align:top; text-align:left; border:0}
}.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 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}
+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 ***/
* {border:0; margin:0; padding:0; font-familiy:Helvetica}
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:strong; 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:string; 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; line-height:2.4em; 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; line-height:1.5em}
.btn.large {padding:1em 2em !important; font-size:1.2em; line-height:4em}
.btn.oval {border-radius:50%}
/*** helpers ***/
.rounded {-moz-border-radius:5px; border-radius:5px}
.padded {padding:10px 20px !important; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box}
.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; width:100%}
.yscroll {overflow-y:scroll; width:100%}
.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=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}
input, textarea, select, button {font-size:12px}
input:not([type]), input:not([type=checkbox]):not([type=radio]):not([type=submit]):hover, select:hover, textarea:hover {background-color:#fbf6d9; transition:background-color 1s ease}
/*** grid ***/
.container {margin-right:-20px}
.container>.quarter, .container>.half, .container>.third, .container>.twothirds, .container>.threequarters, .container>.fill{display:inline-block; padding:5px 20px 5px 0; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; 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:2000}
.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; z-index:2000}
}
@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]:hover:after,[data-tooltip]:hover:before {display:block}
[data-tooltip]:before, [data-tooltip]:after {display:none; position:absolute; top:0}
[data-tooltip]:before {
border-bottom:.6em solid black;
border-bottom:.6em solid black;
border-left:7px solid transparent;
border-right:7px solid transparent;
content:"";
left:20px;
margin-top:1em;
}
[data-tooltip]:after {
background-color:rgba(0,0,0,0.8);
border:4px solid rgba(0,0,0,0.8);
border-radius:7px;
color:white;
content:attr(data-tooltip);
left:0;
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, .tags > span.off:hover {
height: 30px;
padding: 4px 9px;
text-decoration: none;
margin: 0 5px 30px 0 !important;
white-space: nowrap;
color: white;
background-color: #26a69a;
border-radius: 5px;
line-height: 32px;
}
.tags.dismissible > span:not(.off):hover {
background-color: #ccc !important;
}
.tags.dismissible > span:not(.off):after {
content: " \f00d";
font-family: FontAwesome;
}
.tags > span.off {
background-color: #ccc;
}
+21 -139
View File
@@ -1,84 +1,17 @@
/** these MUST stay **/ header a {color: white; font-size:1.1em}
a {text-decoration:none; white-space:nowrap} main {min-height: 70vh}
a:hover {text-decoration:underline} .form-group {padding-bottom: 10px !important;}
a.button {text-decoration:none} .w2p_hidden {display:none;visibility:visible}
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} .right {float:right; text-align:right}
.left {float:left; text-align:left} .left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle} .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_fw {padding-bottom:1px}
td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top} td.w2p_fl,td.w2p_fw,td.w2p_fc {vertical-align:top}
td.w2p_fl {text-align:left} td.w2p_fl {text-align:left}
td.w2p_fl, td.w2p_fw {padding-right:7px} td.w2p_fl, td.w2p_fw {padding-right:7px}
td.w2p_fl,td.w2p_fc {padding-top:4px} td.w2p_fl,td.w2p_fc {padding-top:4px}
div.w2p_export_menu {margin:5px 0} 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;} 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} */ /* tr#submit_record__row {border-top:1px solid #E5E5E5} */
#submit_record__row td {padding-top:.5em} #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_user_form td {vertical-align:top}
/*********** web2py specific ***********/ /*********** web2py specific ***********/
div.flash { div.w2p_flash {
font-weight:bold; font-weight:bold;
display:none; display:none;
position:fixed; padding:20px 20px 20px 50px;
padding:10px; width:100%;
top:48px;
right:250px;
min-width:280px;
opacity:0.95; opacity:0.95;
margin:0px 0px 10px 10px;
vertical-align:middle; vertical-align:middle;
cursor:pointer; cursor:pointer;
color:#fff; color:#000;
background-color:#000; background-color:#ffdc00;
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; 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 .ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;} {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_wrapper {display:block}
div.error { div.error {
width: 298px; color:red;
background:red;
border: 2px solid #d00;
color:white;
padding:5px; padding:5px;
display:inline-block; 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 { .topbar {
@@ -190,34 +99,8 @@ div.error {
*/ */
/* .web2py_table {border:1px solid #ccc} */ /* .web2py_table {border:1px solid #ccc} */
.web2py_paginator {} .web2py_paginator {}
.web2py_grid {width:100%}
.web2py_grid table {width:100%} .web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;} .web2py_grid td {color: black;}
.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 { .web2py_console form {
width: 100%; width: 100%;
@@ -302,11 +185,6 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console input, .web2py_console select, .web2py_console input, .web2py_console select,
.web2py_console a { margin: 2px; } .web2py_console a { margin: 2px; }
.web2py_htmltable {
width: 100%;
overflow-x: auto;
-ms-overflow-x:scroll;
}
#wiki_page_body { #wiki_page_body {
width: 600px; width: 600px;
@@ -317,6 +195,10 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */ /* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2} .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.w2p_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:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px} .ie9 #w2p_query_panel {padding-bottom:2px}
.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 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');});
});
+8 -8
View File
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}} dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="all_keys"> <div class="w2p_hidden" id="all_keys">
{{=total['keys']}} {{=total['keys']}}
</div> </div>
<br /> <br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}} dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="ram_keys"> <div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}} {{=ram['keys']}}
</div> </div>
<br /> <br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}} dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="disk_keys"> <div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}} {{=disk['keys']}}
</div> </div>
<br /> <br />
@@ -249,8 +249,8 @@
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul> </ul>
</div> </div>
<br /> <br />
@@ -1,14 +1,14 @@
{{extend 'layout.html'}} {{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> <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>
<div class="contentleft"> <div>
<div > {{=get_content('main')}}
{{=get_content('main')}} {{=get_content('official')}}
</div> {{=get_content('community')}}
{{=get_content('official')}} {{=get_content('more')}}
{{=get_content('community')}}
{{=get_content('more')}}
</div> </div>
@@ -5,33 +5,59 @@
<h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2> <h2>web2py<sup style="font-size:0.5em;">TM</sup> Download</h2>
<center style="padding:20px"> <center class="spaced">
<table class="downloads"> <table class="twothirds">
<tr> <thead>
<th>For Normal Users</th> <tr>
<th>For Testers</th> <th>For Normal Users</th>
<th>For Developers</th> <th>For Testers</th>
</tr> <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> </thead>
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a></td> <tbody>
<td><a class="btn btn-180 btn-danger" href="http://github.com/web2py/web2py/">Git Repository</a></td> <tr>
</tr> <td>
<tr> <a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_win.zip">For Windows</a>
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a></td> </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></td> <a class="btn btn180 rounded yellow" href="http://www.web2py.com/examples/static/nightly/web2py_win.zip">For Windows</a>
</tr> </td>
<tr> <td>
<td><a class="btn btn-180 btn-success" href="http://www.web2py.com/examples/static/web2py_src.zip">Source Code</a></td> <a class="btn btn180 rounded red" href="http://github.com/web2py/web2py/">Git Repository</a>
<td><a class="btn btn-180 btn-warning" href="http://www.web2py.com/examples/static/nightly/web2py_src.zip">Source Code</a></td> </td>
<td><a class="btn btn-180 btn-danger" href="http://web2py.readthedocs.org/en/latest/">Source code docs</a></td> </tr>
</tr> <tr>
<tr> <td>
<td><a class="btn btn-180 btn-success" href="https://dl.dropbox.com/u/18065445/web2py/web2py_manual_5th.pdf">Manual</a></td> <a class="btn btn180 rounded red" href="http://www.web2py.com/examples/static/web2py_osx.zip">For Mac</a>
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/releases">Change Log</a></td> </td>
<td><a class="btn btn-180" href="https://github.com/web2py/web2py/issues">Report a Bug</a></td> <td>
</tr> <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> </table>
</center> </center>
@@ -32,7 +32,7 @@ def hello1():
return "Hello World" return "Hello World"
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -40,7 +40,7 @@ def hello2():
return T("Hello World") return T("Hello World")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -51,7 +51,7 @@ def hello3():
<b>and view: simple_examples/hello3.html</b> <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')}} {{=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). <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> <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")) return dict(message=T("Hello World"))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=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 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. <li>named arguments and name does not start with '_'. They have a special meaning. See "value=" for INPUT, TEXTAREA, SELECT tags later.
</ul> </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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -86,7 +86,7 @@ def hello6():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -94,7 +94,7 @@ def status():
return dict(toobar=response.toolbar()) return dict(toobar=response.toolbar())
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <br/>Try it here: <a class="btn" href="/{{=request.application}}/simple_examples/status">status</a></p>
<h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -102,7 +102,7 @@ def redirectme():
redirect(URL('hello3')) redirect(URL('hello3'))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can do redirect. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -110,7 +110,7 @@ def raisehttp():
raise HTTP(400,"internal error") raise HTTP(400,"internal error")
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>You can raise HTTP exceptions to return an error page. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -128,7 +128,7 @@ def servejs():
return 'alert("This is a Javascript document, it is not supposed to run!");' return 'alert("This is a Javascript document, it is not supposed to run!");'
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3 id="example_json">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -136,7 +136,7 @@ def servejs():
return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}]) return response.json(['foo', {'bar': ('baz', None, 1.0, 2)}])
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <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 +152,7 @@ def makertf():
response.headers['Content-Type']='text/rtf' response.headers['Content-Type']='text/rtf'
return q.dumps(doc) return q.dumps(doc)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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> <h3 id="example_rss">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
{{=CODE(""" {{=CODE("""
@@ -179,7 +179,7 @@ def rss_aggregator():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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 <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. 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> <h3 id="example_wiki">Example {{=c}}{{c+=1}}</h3><b>In controller: simple_examples.py</b>
@@ -194,7 +194,7 @@ def ajaxwiki_onclick():
return MARKMIN(request.vars.text).xml() return MARKMIN(request.vars.text).xml()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<p>The markmin wiki markup is described <a href="{{=URL('static','markmin.html')}}">here</a>. <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> <h2 id="session_examples">Session Examples</h2>
@@ -207,7 +207,7 @@ def counter():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: session_examples/counter.html</b> """.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')}} {{=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. <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> <h2 id="template_examples">Template Examples</h2>
@@ -219,7 +219,7 @@ def variables():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/variables.html</b> """.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')}} {{=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)). <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -228,7 +228,7 @@ def test_for():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_for.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -237,7 +237,7 @@ def test_if():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_if.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -246,7 +246,7 @@ def test_try():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_try.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -255,7 +255,7 @@ def test_def():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/test_def.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -264,7 +264,7 @@ def escape():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/escape.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -273,7 +273,7 @@ def xml():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/xml.html</b> """.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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: template_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -282,7 +282,7 @@ def beautify():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: template_examples/beautify.html</b> """.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')}} {{=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. <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> <h2 id="layout_examples">Layout Examples</h2>
@@ -298,7 +298,7 @@ def civilized():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/civilized.html</b> """.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')}} {{=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;. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -310,7 +310,7 @@ def slick():
return dict(message="you clicked on 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> """.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')}} {{=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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: layout_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -323,7 +323,7 @@ def basic():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}<b>and view: layout_examples/basic.html</b> """.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')}} {{=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. <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> <h2 id="form_examples">Form Examples</h2>
@@ -347,7 +347,7 @@ def form():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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. 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> <h2 id="database_examples">Database Examples</h2>
@@ -497,7 +497,7 @@ def cache_in_ram():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -508,7 +508,7 @@ def cache_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -519,7 +519,7 @@ def cache_in_ram_and_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
@@ -530,7 +530,7 @@ def cache_in_ram_and_disk():
t=time.ctime() 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')}} 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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -541,7 +541,7 @@ def cache_controller_on_disk():
return dict(time=t,link=A('click to reload',_href=URL(r=request))) return dict(time=t,link=A('click to reload',_href=URL(r=request)))
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -553,7 +553,7 @@ def cache_controller_and_view():
return response.render(d) return response.render(d)
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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! <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: cache_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -583,7 +583,7 @@ def data():
<b>In view: ajax_examples/index.html</b> <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')}} {{=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. <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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -591,7 +591,7 @@ def flash():
response.flash='this text should appear!' response.flash='this text should appear!'
return dict() return dict()
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.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> <h3>Example {{=c}}{{c+=1}}</h3><b>In controller: ajax_examples.py </b>
{{=CODE(""" {{=CODE("""
@@ -600,7 +600,7 @@ def fade():
""".strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}} """.strip(),language='web2py',link=URL('global','vars'),_class='boxCode')}}
<b>In view: ajax_examples/fade.html </b><br/> <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')}} {{=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> <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 Web2py includes a widget that acts like an Excel-like spreadsheet and can be used to build forms
+32 -44
View File
@@ -1,21 +1,8 @@
{{extend 'layout.html'}} {{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="container">
<div class="span12"> <div class="twothirds">
<div class="span8"> <div class="padded">
<h3>web2py<sup>TM</sup> Web Framework</h3> <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> <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%"> <table width="100%">
@@ -39,45 +26,46 @@ random.shuffle(quotes)
</table> </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> <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>
<div class="span4" style="text-align:center"> </div>
<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/> <div class="third">
<a class="btn btn-danger" href="{{=URL('download')}}" style="margin-top:10px; width:180px; color:white">Download Now</a><br/> <div class="padded center">
<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 href="http://www.infoworld.com/slideshow/24605/infoworlds-2012-technology-of-the-year-award-winners-183313#slide23">
<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/> <img src="{{=URL('static','images/infoworld2012.jpeg')}}">
<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> </a>
<a class="btn rounded red fill" href="{{=URL('download')}}">
Download Now
</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>
</div> </div>
<div class="row-fluid"> <div class="container">
<div class="span12"> <div class="third">
<div class="span4"> <div class="padded">
<h3><a href="{{=URL('what')}}">Batteries Included</a></h3> <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> <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>
<div class="span4"> </div>
<h3><a href="http://web2py.com/demo_admin">Web-Based IDE</a></h3> <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> <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>
<div class="span4"> </div>
<h3><a href="{{=URL('documentation')}}">Extensive Docs</a></h3> <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://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> <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>
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="container">
<div class="span12"> <div class="fill padded">
<img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png"> <img class="scale-with-grid centered" src="/examples/static/images/shadow-bottom.png">
</div> </div>
</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> <ul>
<li><a target="_blank" href="http://experts4solutions.com">Experts4Soutions</a> (worldwide)</li> <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.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.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.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.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.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.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.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://www.nrg.com.br">NRG Internet Solutions</a> (Brazil)</li>
<li><a target="_blank" href="http://itjp.net.br/">ITJP</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://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://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://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="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.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.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://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.tasko.it/">Tasko</a> (Italy)</li>
<li><a target="_blank" href="http://www.dutveul.nl">Dutveul</a> (Netherlands)</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> </ul>
</div> </div>
@@ -5,6 +5,7 @@
{{block right_sidebar}} {{block right_sidebar}}
<center> <center>
<!--
<h3 class="feature-title">SITES POWERED BY WEB2PY</h3> <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="img1" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img2" 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="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="img7" width="200px"/></a>
<a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a> <a href="http://web2py.com/poweredby"><img class="frame" id="img8" width="200px"/></a>
</div> -->
</center> </center>
<script> <script>
function showimages() { function showimages() {
+61 -167
View File
@@ -1,173 +1,67 @@
<!--[if HTML5]><![endif]--> <html>
<!DOCTYPE html> <head>
<!-- paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/ --> <meta charset="utf-8">
<!--[if lt IE 7]><html class="ie ie6 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<!--[if IE 7]><html class="ie ie7 ie-lte9 ie-lte8 ie-lte7 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!--[if IE 8]><html class="ie ie8 ie-lte9 ie-lte8 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <meta name="apple-mobile-web-app-capable" content="yes" />
<!--[if IE 9]><html class="ie9 ie-lte9 no-js" lang="{{=T.accepted_language or 'en'}}"> <![endif]--> <link href="{{=URL('static','css/stupid.css')}}" rel="stylesheet" type="text/css"/>
<!--[if (gt IE 9)|!(IE)]><!--> <html class="no-js" lang="{{=T.accepted_language or 'en'}}"> <!--<![endif]--> <link href="{{=URL('static','css/calendar.css')}}" rel="stylesheet" type="text/css"/>
<head> <link href="{{=URL('static','css/web2py.css')}}" rel="stylesheet" type="text/css"/>
<title>{{=response.title or request.application}}</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<!--[if !HTML5]> <style>
<meta http-equiv="X-UA-Compatible" content="IE=edge{{=not request.is_local and ',chrome=1' or ''}}"> th {color: black}
<![endif]--> tbody tr:hover {background-color:transparent}
<!-- www.phpied.com/conditional-comments-block-downloads/ --> tbody tr {border-bottom: none}
<!-- Always force latest IE rendering engine p {text-align: left}
(even in intranet) & Chrome Frame pre {background-color: black!important;border-radius:5px; color:white; padding:10px}
Remove this if you use the .htaccess --> a.btn.btn180 {padding:20px; font-size:1.2em; width:200px!important}
</style>
<meta charset="utf-8" /> {{
left_sidebar_enabled = globals().get('left_sidebar_enabled', False)
<!-- http://dev.w3.org/html5/markup/meta.name.html --> right_sidebar_enabled = globals().get('right_sidebar_enabled', False)
<meta name="application-name" content="{{=request.application}}" /> middle_column = {0: 'fill', 1: 'threequarters', 2: 'half'}[
<!-- 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'}[
(left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)] (left_sidebar_enabled and 1 or 0)+(right_sidebar_enabled and 1 or 0)]
}} }}
{{include "web2py_ajax.html"}}
<!-- uncomment here to load jquery-ui </head>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" type="text/css" media="all" /> <body class="black">
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script> <header class="black padded">
uncomment to load jquery-ui //--> <div class="container middle max900">
<noscript><link href="{{=URL('static', 'css/web2py_bootstrap_nojs.css')}}" rel="stylesheet" type="text/css" /></noscript> <div class="fill middle">
{{block head}}{{end}} <label class="ham padded fa fa-bars" for="menu"></label>
</head> <div class="burger accordion">
<input type="checkbox" id="menu"/>
<body> {{=MENU(response.menu,_class='menu')}}
<!-- Navbar ================================================== --> </div>
<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" />
</div> </div>
</div> </div>
</header> </header>
</div> {{if response.flash:}}
<div class="container"> <div class="w2p_flash">
{{=response.flash}}
<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>
</div> </div>
{{pass}}
</div> <!-- /container --> <main class="white">
<div class="container max900">
<!-- The javascript ============================================= {{if left_sidebar_enabled:}}
(Placed at the end of the document so the pages load faster) --> <div class="quarter padded">{{block left_sidebar}}{{end}}</div>
<script src="{{=URL('static','js/bootstrap.min.js')}}"></script> {{pass}}
<script src="{{=URL('static','js/web2py_bootstrap.js')}}"></script> <div class="{{=middle_column}} padded">{{include}}</div>
<!--[if lt IE 7 ]> {{if right_sidebar_enabled:}}
<script src="{{=URL('static','js/dd_belatedpng.js')}}"></script> <div class="quarter padded">{{block right_sidebar}}{{end}}</div>
<script> DD_belatedPNG.fix('img, .png_bg'); //fix any <img> or .png_bg background-images </script> {{pass}}
<![endif]--> </div>
</main>
{{if response.google_analytics_id:}} <footer class="black">
<script src="{{=URL('static','js/analytics.min.js')}}"></script> <div class="container padded max900">
<script type="text/javascript"> <div class="fill">
analytics.initialize({ Copyright @ 2016 - Powered by Web2py
'Google Analytics':{trackingId:'{{=response.google_analytics_id}}'} </div>
});</script> </div>
{{pass}} </footer>
<script src="{{=URL('static','js/share.js',vars=dict(static=URL('static','images')))}}"></script> </body>
<a style="position:fixed;bottom:0;left:0;z-index:1000" href="https://groups.google.com/forum/?fromgroups#!forum/web2py" target="_blank"> <script>
<img src="{{=URL('static','images/questions.png')}}" /> // prevent android horizontal scrolling
</a> window.addEventListener("scroll", function(){window.scroll(0, window.pageYOffset);}, false);
</script>
</body>
</html> </html>
+1 -1
View File
@@ -576,7 +576,7 @@ def bg_graph_model():
meta_graphmodel = dict(group=request.application, color='#ECECEC') meta_graphmodel = dict(group=request.application, color='#ECECEC')
group = meta_graphmodel['group'].replace(' ', '') group = meta_graphmodel['group'].replace(' ', '')
if not subgraphs.has_key(group): if group not in subgraphs:
subgraphs[group] = dict(meta=meta_graphmodel, tables=[]) subgraphs[group] = dict(meta=meta_graphmodel, tables=[])
subgraphs[group]['tables'].append(tablename) subgraphs[group]['tables'].append(tablename)
+1 -1
View File
@@ -29,12 +29,12 @@ def user():
http://..../[app]/default/user/profile http://..../[app]/default/user/profile
http://..../[app]/default/user/retrieve_password http://..../[app]/default/user/retrieve_password
http://..../[app]/default/user/change_password http://..../[app]/default/user/change_password
http://..../[app]/default/user/manage_users (requires membership in
http://..../[app]/default/user/bulk_register http://..../[app]/default/user/bulk_register
use @auth.requires_login() use @auth.requires_login()
@auth.requires_membership('group name') @auth.requires_membership('group name')
@auth.requires_permission('read','table name',record_id) @auth.requires_permission('read','table name',record_id)
to decorate functions that need access control to decorate functions that need access control
also notice there is http://..../[app]/appadmin/manage/auth to allow administrator to manage users
""" """
return dict(form=auth()) return dict(form=auth())
+12 -8
View File
@@ -14,10 +14,12 @@ from gluon.contrib.appconfig import AppConfig
## once in production, remove reload=True to gain full speed ## once in production, remove reload=True to gain full speed
myconf = AppConfig(reload=True) myconf = AppConfig(reload=True)
if not request.env.web2py_runtime_gae: if not request.env.web2py_runtime_gae:
## if NOT running on Google App Engine use SQLite or other DB ## 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: else:
## connect to Google BigTable (optional 'google:datastore://namespace') ## connect to Google BigTable (optional 'google:datastore://namespace')
db = DAL('google:datastore+ndb') db = DAL('google:datastore+ndb')
@@ -32,8 +34,8 @@ else:
## none otherwise. a pattern can be 'controller/function.extension' ## none otherwise. a pattern can be 'controller/function.extension'
response.generic_patterns = ['*'] if request.is_local else [] response.generic_patterns = ['*'] if request.is_local else []
## choose a style for forms ## choose a style for forms
response.formstyle = myconf.take('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other response.formstyle = myconf.get('forms.formstyle') # or 'bootstrap3_stacked' or 'bootstrap2' or other
response.form_label_separator = myconf.take('forms.separator') response.form_label_separator = myconf.get('forms.separator') or ''
## (optional) optimize handling of static files ## (optional) optimize handling of static files
@@ -53,7 +55,7 @@ response.form_label_separator = myconf.take('forms.separator')
from gluon.tools import Auth, Service, PluginManager from gluon.tools import Auth, Service, PluginManager
auth = Auth(db) auth = Auth(db, host=myconf.get('host.name'))
service = Service() service = Service()
plugins = PluginManager() plugins = PluginManager()
@@ -62,9 +64,11 @@ auth.define_tables(username=False, signature=False)
## configure email ## configure email
mail = auth.settings.mailer mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.take('smtp.server') mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.take('smtp.sender') mail.settings.sender = myconf.get('smtp.sender')
mail.settings.login = myconf.take('smtp.login') 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 ## configure auth policy
auth.settings.registration_requires_verification = False auth.settings.registration_requires_verification = False
+4 -4
View File
@@ -12,10 +12,10 @@ response.title = request.application.replace('_',' ').title()
response.subtitle = '' response.subtitle = ''
## read more at http://dev.w3.org/html5/markup/meta.name.html ## read more at http://dev.w3.org/html5/markup/meta.name.html
response.meta.author = 'Your Name <you@example.com>' response.meta.author = myconf.get('app.author')
response.meta.description = 'a cool new app' response.meta.description = myconf.get('app.description')
response.meta.keywords = 'web2py, python, framework' response.meta.keywords = myconf.get('app.keywords')
response.meta.generator = 'Web2py Web Framework' response.meta.generator = myconf.get('app.generator')
## your http://google.com/analytics id ## your http://google.com/analytics id
response.google_analytics_id = None response.google_analytics_id = None
+14 -3
View File
@@ -1,17 +1,28 @@
; App configuration ; 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]
name = localhost
; db configuration ; db configuration
[db] [db]
uri = sqlite://storage.sqlite uri = sqlite://storage.sqlite
migrate = 1 migrate = true
pool_size = 1 pool_size = 10 ; ignored for sqlite
; smtp address and credentials ; smtp address and credentials
[smtp] [smtp]
server = smtp.gmail.com:587 server = smtp.gmail.com:587
sender = you@gmail.com sender = you@gmail.com
login = username:password login = username:password
tls = true
ssl = true
; form styling ; form styling
[forms] [forms]
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
div.flash { div.w2p_flash {
background-image: none; background-image: none;
border-radius: 4px; border-radius: 4px;
-o-border-radius: 4px; -o-border-radius: 4px;
@@ -15,13 +15,13 @@ div.flash {
margin: 0 0 20px; margin: 0 0 20px;
padding: 15px 35px 15px 15px; padding: 15px 35px 15px 15px;
} }
div.flash.alert:hover { div.w2p_flash.alert:hover {
opacity: 1; opacity: 1;
} }
.ie-lte8 div.flash { .ie-lte8 div.w2p_flash {
filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0); filter: progid: DXImageTransform.Microsoft.gradient(startColorstr='#222222', endColorstr='#000000', GradientType=0);
} }
.ie-lte8 div.flash:hover { .ie-lte8 div.w2p_flash:hover {
filter: alpha(opacity=25); filter: alpha(opacity=25);
} }
.main-container { .main-container {
@@ -37,7 +37,7 @@ div.error {
display: inline-block; display: inline-block;
padding: 5px; padding: 5px;
} }
div.flash.alert { div.w2p_flash.alert {
display: none; display: none;
position: fixed; position: fixed;
top: 70px; top: 70px;
@@ -108,7 +108,7 @@ select.autocomplete {
background: url(../images/background.jpg) no-repeat center center; background: url(../images/background.jpg) no-repeat center center;
} }
body { body {
padding-top: 50px; padding-top: 60px;
margin-bottom: 60px; margin-bottom: 60px;
} }
header { header {
@@ -136,7 +136,7 @@ header h1 {
header .jumbotron { header .jumbotron {
background-color: transparent; background-color: transparent;
} }
.flash { .w2p_flash {
opacity: 0.9!important; opacity: 0.9!important;
right: 100px; right: 100px;
} }
@@ -233,7 +233,7 @@ div.error_wrapper {
line-height: 20px; line-height: 20px;
margin-right: 2px; margin-right: 2px;
display: inline-block; display: inline-block;
padding: 3px 5px; padding: 6px 12px;
} }
.web2py_counter { .web2py_counter {
margin-top: 5px; margin-top: 5px;
@@ -270,6 +270,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console select, .web2py_console select,
.web2py_console a { .web2py_console a {
margin: 2px; margin: 2px;
padding: 6px 12px;
} }
#wiki_page_body { #wiki_page_body {
width: 600px; width: 600px;
@@ -285,7 +286,7 @@ li.w2p_grid_breadcrumb_elem {
.web2py_console .form-control { .web2py_console .form-control {
width: 20%; width: 20%;
display: inline; display: inline;
height: 100%; height: 32px;
} }
.web2py_console #w2p_keywords { .web2py_console #w2p_keywords {
width: 50%; width: 50%;
@@ -314,6 +315,3 @@ td.w2p_fc,
input[type=checkbox], input[type=radio] { input[type=checkbox], input[type=radio] {
margin: 4px 4px 0 0; margin: 4px 4px 0 0;
} }
.btn {
margin-right: 4px;
}
+6 -6
View File
@@ -33,7 +33,7 @@ audio {width:200px}
[type="text"], [type="password"], select { [type="text"], [type="password"], select {
margin-right: 5px; width: 300px; margin-right: 5px; width: 300px;
} }
.hidden {display:none;visibility:visible} .w2p_hidden {display:none;visibility:visible}
.right {float:right; text-align:right} .right {float:right; text-align:right}
.left {float:left; text-align:left} .left {float:left; text-align:left}
.center {width:100%; text-align:center; vertical-align:middle} .center {width:100%; text-align:center; vertical-align:middle}
@@ -88,7 +88,7 @@ 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_user_form td {vertical-align:top}
/*********** web2py specific ***********/ /*********** web2py specific ***********/
div.flash { div.w2p_flash {
font-weight:bold; font-weight:bold;
display:none; display:none;
position:fixed; position:fixed;
@@ -117,11 +117,11 @@ div.flash {
z-index:2000; z-index:2000;
} }
div.flash #closeflash{color:inherit; float:right; margin-left:15px;} div.w2p_flash #closeflash{color:inherit; float:right; margin-left:15px;}
.ie-lte7 div.flash #closeflash .ie-lte7 div.flash #closeflash
{color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;} {color:expression(this.parentNode.currentStyle['color']);float:none;position:absolute;right:4px;}
div.flash:hover { opacity:0.25; } div.w2p_flash:hover { opacity:0.25; }
div.error_wrapper {display:block} div.error_wrapper {display:block}
div.error { div.error {
@@ -304,8 +304,8 @@ li.w2p_grid_breadcrumb_elem {
/* fix some IE problems */ /* fix some IE problems */
.ie-lte7 .topbar .container {z-index:2} .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.w2p_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:hover {filter:alpha(opacity=25);}
.ie9 #w2p_query_panel {padding-bottom:2px} .ie9 #w2p_query_panel {padding-bottom:2px}
.web2py_console .form-control {width: 20%; display: inline;} .web2py_console .form-control {width: 20%; display: inline;}
@@ -6,9 +6,9 @@
* this over and over... all will be bound to the document * this over and over... all will be bound to the document
*/ */
/*adds btn class to buttons*/ /*adds btn class to buttons*/
$('button', target).addClass('btn btn-default'); $('button:not([class^="btn"])', target).addClass('btn btn-default');
$("p.w2p-autocomplete-widget input").addClass('form-control'); $("p.w2p-autocomplete-widget input").addClass('form-control');
$('form input[type="submit"], form input[type="button"]', target).addClass('btn btn-default'); $('form input[type="submit"]:not([class^="btn"]), form input[type="button"]:not([class^="btn"])', target).addClass('btn btn-default');
/* javascript for PasswordWidget*/ /* javascript for PasswordWidget*/
$('input[type=password][data-w2p_entropy]', target).each(function() { $('input[type=password][data-w2p_entropy]', target).each(function() {
web2py.validate_entropy($(this)); web2py.validate_entropy($(this));
@@ -18,9 +18,9 @@
function pe(ul, e) { function pe(ul, e) {
var new_line = ml(ul); var new_line = ml(ul);
rel(ul); rel(ul);
if ($(e.target).parent().is(':visible')) { if ($(e.target).closest('li').is(':visible')) {
/* make sure we didn't delete the element before we insert after */ /* make sure we didn't delete the element before we insert after */
new_line.insertAfter($(e.target).parent()); new_line.insertAfter($(e.target).closest('li'));
} else { } else {
/* the line we clicked on was deleted, just add to end of list */ /* the line we clicked on was deleted, just add to end of list */
new_line.appendTo(ul); new_line.appendTo(ul);
@@ -30,9 +30,9 @@
} }
function rl(ul, e) { function rl(ul, e) {
if ($(ul).children().length > 1) { if ($(ul).find('li').length > 1) {
/* only remove if we have more than 1 item so the list is never empty */ /* only remove if we have more than 1 item so the list is never empty */
$(e.target).parent().remove(); $(e.target).closest('li').remove();
} }
} }
@@ -46,13 +46,13 @@
function rel(ul) { function rel(ul) {
/* keep only as many as needed*/ /* keep only as many as needed*/
$(ul).find("li").each(function() { $(ul).find("li").each(function() {
var trimmed = $.trim($(this.firstChild).val()); var trimmed = $.trim($(this).find(":text").val());
if (trimmed == '') $(this).remove(); if (trimmed == '') $(this).remove();
else $(this.firstChild).val(trimmed); else $(this).find(":text").val(trimmed);
}); });
} }
var ul = this; var ul = this;
$(ul).find(":text").after('<a class="btn btn-default" href="#">+</a>&nbsp;<a class="btn btn-default" href="#">-</a>').keypress(function(e) { $(ul).find(":text").addClass('form-control').wrap("<div class='input-group'></div>").after('<div class="input-group-addon"><i class="glyphicon glyphicon-plus"></i></div><div class="input-group-addon"><i class="glyphicon glyphicon-minus"></i></div>').keypress(function(e) {
return (e.which == 13) ? pe(ul, e) : true; return (e.which == 13) ? pe(ul, e) : true;
}).next().click(function(e) { }).next().click(function(e) {
pe(ul, e); pe(ul, e);
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -155,8 +155,8 @@
{{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("Cache contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}} dict(hours=total['oldest'][0], min=total['oldest'][1], sec=total['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Cache Keys'), _onclick='jQuery("#all_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="all_keys"> <div class="w2p_hidden" id="all_keys">
{{=total['keys']}} {{=total['keys']}}
</div> </div>
<br /> <br />
@@ -183,8 +183,8 @@
{{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("RAM contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}} dict(hours=ram['oldest'][0], min=ram['oldest'][1], sec=ram['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('RAM Cache Keys'), _onclick='jQuery("#ram_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="ram_keys"> <div class="w2p_hidden" id="ram_keys">
{{=ram['keys']}} {{=ram['keys']}}
</div> </div>
<br /> <br />
@@ -212,8 +212,8 @@
{{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.", {{=T.M("DISK contains items up to **%(hours)02d** %%{hour(hours)} **%(min)02d** %%{minute(min)} **%(sec)02d** %%{second(sec)} old.",
dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}} dict(hours=disk['oldest'][0], min=disk['oldest'][1], sec=disk['oldest'][2]))}}
</p> </p>
{{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "hidden" );')}} {{=BUTTON(T('Disk Cache Keys'), _onclick='jQuery("#disk_keys").toggle().toggleClass( "w2p_hidden" );')}}
<div class="hidden" id="disk_keys"> <div class="w2p_hidden" id="disk_keys">
{{=disk['keys']}} {{=disk['keys']}}
</div> </div>
<br /> <br />
@@ -249,8 +249,8 @@
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['png'])}}">png</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['svg'])}}">svg</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['pdf'])}}">pdf</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['ps'])}}">ps</a></li>
<li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li> <li><a href="{{=URL('appadmin', 'bg_graph_model', args=['dot'])}}">dot</a></li>
</ul> </ul>
</div> </div>
<br /> <br />
+2 -3
View File
@@ -21,7 +21,6 @@
<meta name="google-site-verification" content=""> <meta name="google-site-verification" content="">
<!-- include stylesheets --> <!-- include stylesheets -->
<link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/> <link rel="stylesheet" href="{{=URL('static','css/bootstrap.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/bootstrap-theme.min.css')}}"/>
<link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/> <link rel="stylesheet" href="{{=URL('static','css/web2py-bootstrap3.css')}}"/>
<link rel="shortcut icon" href="{{=URL('static','images/favicon.ico')}}" type="image/x-icon"> <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')}}"> <link rel="apple-touch-icon" href="{{=URL('static','images/favicon.png')}}">
@@ -47,9 +46,9 @@
</head> </head>
<body> <body>
<!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]--> <!--[if lt IE 8]><p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p><![endif]-->
<div class="flash alert alert-dismissable">{{=response.flash or ''}}</div> <div class="w2p_flash alert alert-dismissable">{{=response.flash or ''}}</div>
<!-- Navbar ======================================= --> <!-- Navbar ======================================= -->
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid"> <div class="container-fluid">
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
+1 -1
View File
@@ -120,7 +120,7 @@ args=()
class=handlers.RotatingFileHandler class=handlers.RotatingFileHandler
level=DEBUG level=DEBUG
formatter=simpleFormatter formatter=simpleFormatter
args=("logs/web2py.log", "a", 1000000, 5) args=("web2py.log", "a", 1000000, 5)
[handler_osxSysLogHandler] [handler_osxSysLogHandler]
class=handlers.SysLogHandler class=handlers.SysLogHandler
Vendored
+157
View File
@@ -0,0 +1,157 @@
from fabric.api import *
from fabric.operations import put, get
from fabric.contrib.files import exists
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 :')
INSTALL_SCRIPT = "setup-web2py-nginx-uwsgi-ubuntu.sh"
now = datetime.datetime.now()
applications = '/home/www-data/web2py/applications'
def create_user(username):
"""fab -H root@host create_user:username"""
password = getpass.getpass(name+' password for %s> ' % username)
run('useradd -m %s' % username)
run('usermod --password %s %s' % (crypt.crypt(password, 'salt'), username))
run('mkdir -p ~%s/.ssh' % username)
run('cp /etc/sudoers /tmp/sudoers.new')
append('/tmp/sudoers.new', '%s ALL=NOPASSWD: ALL' % username, use_sudo=True)
run('visudo -c -f /tmp/sudoers.new')
run('EDITOR="cp /tmp/sudoers.new" visudo')
uncomment('~%s/.bashrc' % username, '#force_color_prompt=yes')
local('ssh-copy-id %s' % env.hosts[0])
def install_web2py():
"""fab -H username@host install_web2py"""
sudo('wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/%s' % INSTALL_SCRIPT)
sudo('chmod +x %s' % INSTALL_SCRIPT)
sudo('./'+INSTALL_SCRIPT)
def start_webserver():
sudo('service nginx start')
sudo('start uwsgi-emperor')
sudo('start web2py-scheduler')
def stop_webserver():
sudo('stop uwsgi-emperor')
sudo('service nginx stop')
sudo('stop web2py-scheduler')
def restart_webserver():
stop_webserver()
start_webserver()
def notify(appname=None):
"""fab -H username@host notify:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
sudo('echo "response.flash = \'System Going Down For Maintenance\'" > models/flash_goingdown.py')
def down(appname=None):
"""fab -H username@host down:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
sudo('echo `date` > DISABLED')
sudo('rm -rf sessions/* || true')
def up(appname=None):
"""fab -H username@host up:appname"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
with cd(appfolder):
if exists('modules/flash_goingdown.py'):
sudo('rm modules/flash_goingdown.py')
sudo('rm DISABLED')
def mkdir_or_backup(appname):
appfolder = applications+'/'+appname
if not exists(appfolder):
sudo('mkdir %s' % appfolder)
sudo('chown -R www-data:www-data %s' % appfolder)
backup = None
else:
dt = now.strftime('%y-%m-%d-%h-%m')
backup = '%s.%s.zip' % (appname, dt)
with cd(applications):
sudo('zip -r %s %s' % (backup, appname))
return backup
def git_deploy(appname, repo):
"""fab -H username@host git_deploy:appname,username/remoname"""
appfolder = applications+'/'+appname
backup = mkdir_or_backup(appname)
if exists(appfolder):
with cd(appfolder):
sudo('git pull origin master')
sudo('chown -R www-data:www-data *')
else:
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]
appfolder = applications+'/'+appname
filename = '%s.zip' % appname
with cd(appfolder):
sudo('zip -r /tmp/%s *' % filename)
get('/tmp/%s' % filename, filename)
sudo('rm /tmp/%s' % filename)
local('unzip %s' % filename)
local('rm %s' % filename)
def deploy(appname=None, all=False):
"""fab -H username@host deploy:appname,all"""
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications+'/'+appname
if os.path.exists('_update.zip'):
os.unlink('_update.zip')
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')
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
def restore(backup):
"""fab -H username@host restore:backupfilename"""
appname = backup.split('/')[-1].split('.')[0]
appfolder = applications + '/' + appname
with cd(appfolder):
sudo('rm -r *')
with cd(applications):
sudo('unzip %s' % backup)
sudo('chown -R www-data:www-data %s' % appname)
def cleanup(appname):
appname = appname or os.path.split(os.getcwd())[-1]
appfolder = applications + '/' + appname
with cd(appfolder):
sudo('rm -rf sessions/* || true')
sudo('rm -rf errors/* || true')
sudo('rm -rf cache/* || true')
+4 -4
View File
@@ -120,7 +120,7 @@ def app_cleanup(app, request):
r = False r = False
# Remove cache files # Remove cache files
path = apath('%s/cache/' % app, request) path = apath('%s/cache/' % app, request)
if os.path.exists(path): if os.path.exists(path):
CacheOnDisk(folder=path).clear() CacheOnDisk(folder=path).clear()
for f in os.listdir(path): for f in os.listdir(path):
@@ -131,7 +131,7 @@ def app_cleanup(app, request):
return r return r
def app_compile(app, request): def app_compile(app, request, skip_failed_views=False):
"""Compiles the application """Compiles the application
Args: Args:
@@ -145,8 +145,8 @@ def app_compile(app, request):
from compileapp import compile_application, remove_compiled_application from compileapp import compile_application, remove_compiled_application
folder = apath(app, request) folder = apath(app, request)
try: try:
compile_application(folder) failed_views = compile_application(folder, skip_failed_views)
return None return failed_views
except (Exception, RestrictedError): except (Exception, RestrictedError):
tb = traceback.format_exc(sys.exc_info) tb = traceback.format_exc(sys.exc_info)
remove_compiled_application(folder) remove_compiled_application(folder)
+14 -10
View File
@@ -58,7 +58,7 @@ def remove_oldest_entries(storage, percentage=90):
# compute current memory usage (%) # compute current memory usage (%)
old_mem = psutil.virtual_memory().percent old_mem = psutil.virtual_memory().percent
# if we have data in storage and utilization exceeds 90% # if we have data in storage and utilization exceeds 90%
while storage and old_mem > percentage: while storage and old_mem > percentage:
# removed oldest entry # removed oldest entry
storage.popitem(last=False) storage.popitem(last=False)
# garbage collect # garbage collect
@@ -378,7 +378,7 @@ class CacheOnDisk(CacheAbstract):
def safe_apply(self, key, function, default_value=None): def safe_apply(self, key, function, default_value=None):
""" """
Safely apply a function to the value of a key in storage and set Safely apply a function to the value of a key in storage and set
the return value of the function to it. the return value of the function to it.
@@ -606,22 +606,26 @@ class Cache(object):
def wrapped_f(): def wrapped_f():
if current.request.env.request_method != 'GET': if current.request.env.request_method != 'GET':
return func() return func()
if quick:
session_ = True if 'S' in quick else False
vars_ = True if 'V' in quick else False
lang_ = True if 'L' in quick else False
user_agent_ = True if 'U' in quick else False
public_ = True if 'P' in quick else False
else:
(session_, vars_, lang_, user_agent_, public_) = \
(session, vars, lang, user_agent, public)
if time_expire: if time_expire:
cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire) cache_control = 'max-age=%(time_expire)s, s-maxage=%(time_expire)s' % dict(time_expire=time_expire)
if quick:
session_ = True if 'S' in quick else False
vars_ = True if 'V' in quick else False
lang_ = True if 'L' in quick else False
user_agent_ = True if 'U' in quick else False
public_ = True if 'P' in quick else False
else:
session_, vars_, lang_, user_agent_, public_ = session, vars, lang, user_agent, public
if not session_ and public_: if not session_ and public_:
cache_control += ', public' cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT') expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
else: else:
cache_control += ', private' cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT' expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model: if cache_model:
#figure out the correct cache key #figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view] cache_key = [current.request.env.path_info, current.response.view]
+37 -34
View File
@@ -464,22 +464,28 @@ def read_pyc(filename):
return marshal.loads(data[8:]) return marshal.loads(data[8:])
def compile_views(folder): def compile_views(folder, skip_failed_views=False):
""" """
Compiles all the views in the application specified by `folder` Compiles all the views in the application specified by `folder`
""" """
path = pjoin(folder, 'views') path = pjoin(folder, 'views')
failed_views = []
for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'): for fname in listdir(path, '^[\w/\-]+(\.\w+)*$'):
try: try:
data = parse_template(fname, path) data = parse_template(fname, path)
except Exception, e: except Exception, e:
raise Exception("%s in %s" % (e, fname)) if skip_failed_views:
filename = 'views.%s.py' % fname.replace(os.path.sep, '.') failed_views.append(fname)
filename = pjoin(folder, 'compiled', filename) else:
write_file(filename, data) raise Exception("%s in %s" % (e, fname))
save_pyc(filename) else:
os.unlink(filename) filename = ('views/%s.py' % fname).replace('/', '_').replace('\\', '_')
filename = pjoin(folder, 'compiled', filename)
write_file(filename, data)
save_pyc(filename)
os.unlink(filename)
return failed_views if failed_views else None
def compile_models(folder): def compile_models(folder):
@@ -652,7 +658,7 @@ def run_view_in(environment):
""" """
request = current.request request = current.request
response = current.response response = current.response
view = response.view view = environment['response'].view
folder = request.folder folder = request.folder
path = pjoin(folder, 'compiled') path = pjoin(folder, 'compiled')
badv = 'invalid view (%s)' % view badv = 'invalid view (%s)' % view
@@ -667,32 +673,28 @@ def run_view_in(environment):
ccode = parse_template(view, pjoin(folder, 'views'), ccode = parse_template(view, pjoin(folder, 'views'),
context=environment) context=environment)
restricted(ccode, environment, 'file stream') restricted(ccode, environment, 'file stream')
elif os.path.exists(path):
x = view.replace('/', '.')
files = ['views.%s.pyc' % x]
if allow_generic:
files.append('views.generic.%s.pyc' % request.extension)
# for backward compatibility
x = view.replace('/', '_')
files.append('views_%s.pyc' % x)
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
filename = pjoin(path, f)
if os.path.exists(filename):
code = read_pyc(filename)
restricted(code, environment, layer=filename)
return
raise HTTP(404,
rewrite.THREAD_LOCAL.routes.error_message % badv,
web2py_error=badv)
else: else:
filename = pjoin(folder, 'views', view) filename = pjoin(folder, 'views', view)
if os.path.exists(path): # compiled views
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)):
if allow_generic:
files.append('views_generic.%s.pyc' % request.extension)
# for backward compatibility
if request.extension == 'html':
files.append('views_%s.pyc' % x[:-5])
if allow_generic:
files.append('views_generic.pyc')
# end backward compatibility code
for f in files:
compiled = pjoin(path, f)
if os.path.exists(compiled):
code = read_pyc(compiled)
restricted(code, environment, layer=compiled)
return
if not os.path.exists(filename) and allow_generic: if not os.path.exists(filename) and allow_generic:
view = 'generic.' + request.extension view = 'generic.' + request.extension
filename = pjoin(folder, 'views', view) filename = pjoin(folder, 'views', view)
@@ -726,7 +728,7 @@ def remove_compiled_application(folder):
pass pass
def compile_application(folder): def compile_application(folder, skip_failed_views=False):
""" """
Compiles all models, views, controller for the application in `folder`. Compiles all models, views, controller for the application in `folder`.
""" """
@@ -734,7 +736,8 @@ def compile_application(folder):
os.mkdir(pjoin(folder, 'compiled')) os.mkdir(pjoin(folder, 'compiled'))
compile_models(folder) compile_models(folder)
compile_controllers(folder) compile_controllers(folder)
compile_views(folder) failed_views = compile_views(folder, skip_failed_views)
return failed_views
def test(): def test():
+21 -1
View File
@@ -35,7 +35,6 @@ from gluon.serializers import json_parser
locker = thread.allocate_lock() locker = thread.allocate_lock()
def AppConfig(*args, **vars): def AppConfig(*args, **vars):
locker.acquire() locker.acquire()
@@ -59,6 +58,27 @@ class AppConfigDict(dict):
dict.__init__(self, *args, **kwargs) dict.__init__(self, *args, **kwargs)
self.int_cache = {} 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 value.split(', ')
else:
try:
return float(value)
except:
return value
except:
return default
def take(self, path, cast=None): def take(self, path, cast=None):
parts = path.split('.') parts = path.split('.')
if path in self.int_cache: if path in self.int_cache:
+1 -1
View File
@@ -93,7 +93,7 @@ def video(url):
def googledoc_viewer(url): def googledoc_viewer(url):
return '<iframe src="http://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url) return '<iframe src="https://docs.google.com/viewer?url=%s&embedded=true" style="max-width:100%%"></iframe>' % urllib.quote(url)
def web2py_component(url): def web2py_component(url):
+11 -4
View File
@@ -27,10 +27,10 @@ from gluon import current
class RESIZE(object): 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'): error_message=' image resize'):
(self.nx, self.ny, self.quality, self.error_message) = ( (self.nx, self.ny, self.quality, self.error_message, self.padding) = (
nx, ny, quality, error_message) nx, ny, quality, error_message, padding)
def __call__(self, value): def __call__(self, value):
if isinstance(value, str) and len(value) == 0: if isinstance(value, str) and len(value) == 0:
@@ -41,7 +41,14 @@ class RESIZE(object):
img = Image.open(value.file) img = Image.open(value.file)
img.thumbnail((self.nx, self.ny), Image.ANTIALIAS) img.thumbnail((self.nx, self.ny), Image.ANTIALIAS)
s = cStringIO.StringIO() 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) s.seek(0)
value.file = s value.file = s
except: except:
+85 -106
View File
@@ -14,12 +14,20 @@ except Exception, e:
raise e raise e
def ldap_auth(server='ldap', port=None, def ldap_auth(server='ldap',
port=None,
base_dn='ou=users,dc=domain,dc=com', base_dn='ou=users,dc=domain,dc=com',
mode='uid', secure=False, mode='uid',
cert_path=None, cert_file=None, secure=False,
cacert_path=None, cacert_file=None, key_file=None, self_signed_certificate=None, # See NOTE below
bind_dn=None, bind_pw=None, filterstr='objectClass=*', cert_path=None,
cert_file=None,
cacert_path=None,
cacert_file=None,
key_file=None,
bind_dn=None,
bind_pw=None,
filterstr='objectClass=*',
username_attrib='uid', username_attrib='uid',
custom_scope='subtree', custom_scope='subtree',
allowed_groups=None, allowed_groups=None,
@@ -159,6 +167,14 @@ def ldap_auth(server='ldap', port=None,
You can set the logging level with the "logging_level" parameter, default You can set the logging level with the "logging_level" parameter, default
is "error" and can be set to error, warning, info, debug. is "error" and can be set to error, warning, info, debug.
""" """
if self_signed_certificate:
# NOTE : If you have a self-signed SSL Certificate pointing over "port=686" and "secure=True" alone
# will not work, you need also to set "self_signed_certificate=True".
# Ref1: https://onemoretech.wordpress.com/2015/06/25/connecting-to-ldap-over-self-signed-tls-with-python/
# Ref2: http://bneijt.nl/blog/post/connecting-to-ldaps-with-self-signed-cert-using-python/
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
logger = logging.getLogger('web2py.auth.ldap_auth') logger = logging.getLogger('web2py.auth.ldap_auth')
if logging_level == 'error': if logging_level == 'error':
logger.setLevel(logging.ERROR) logger.setLevel(logging.ERROR)
@@ -196,8 +212,7 @@ def ldap_auth(server='ldap', port=None,
logger.warning('blank password not allowed') logger.warning('blank password not allowed')
return False return False
logger.debug('mode: [%s] manage_user: [%s] custom_scope: [%s]' logger.debug('mode: [%s] manage_user: [%s] custom_scope: [%s]'
' manage_groups: [%s]' % (str(mode), str(manage_user), ' manage_groups: [%s]' % (str(mode), str(manage_user), str(custom_scope), str(manage_groups)))
str(custom_scope), str(manage_groups)))
if manage_user: if manage_user:
if user_firstname_attrib.count(':') > 0: if user_firstname_attrib.count(':') > 0:
(user_firstname_attrib, (user_firstname_attrib,
@@ -246,14 +261,10 @@ def ldap_auth(server='ldap', port=None,
# in the ldap_basedn # in the ldap_basedn
requested_attrs = ['sAMAccountName'] requested_attrs = ['sAMAccountName']
if manage_user: if manage_user:
requested_attrs.extend([user_firstname_attrib, requested_attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
user_lastname_attrib,
user_mail_attrib])
result = con.search_ext_s( result = con.search_ext_s(
ldap_basedn, ldap.SCOPE_SUBTREE, ldap_basedn, ldap.SCOPE_SUBTREE,
"(&(sAMAccountName=%s)(%s))" % ( "(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare), filterstr),
ldap.filter.escape_filter_chars(username_bare),
filterstr),
requested_attrs)[0][1] requested_attrs)[0][1]
if not isinstance(result, dict): if not isinstance(result, dict):
# result should be a dict in the form # result should be a dict in the form
@@ -286,25 +297,21 @@ def ldap_auth(server='ldap', port=None,
if manage_user: if manage_user:
result = con.search_s(dn, ldap.SCOPE_BASE, result = con.search_s(dn, ldap.SCOPE_BASE,
"(objectClass=*)", "(objectClass=*)",
[user_firstname_attrib, [user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
user_lastname_attrib,
user_mail_attrib])[0][1]
if ldap_mode == 'uid': if ldap_mode == 'uid':
# OpenLDAP (UID) # OpenLDAP (UID)
if ldap_binddn and ldap_bindpw: if ldap_binddn and ldap_bindpw:
con.simple_bind_s(ldap_binddn, ldap_bindpw) con.simple_bind_s(ldap_binddn, ldap_bindpw)
dn = "uid=" + username + "," + ldap_basedn dn = "uid=" + username + "," + ldap_basedn
dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)"%username, [''])[0][0] dn = con.search_s(ldap_basedn, ldap.SCOPE_SUBTREE, "(uid=%s)" % username, [''])[0][0]
else: else:
dn = "uid=" + username + "," + ldap_basedn dn = "uid=" + username + "," + ldap_basedn
con.simple_bind_s(dn, password) con.simple_bind_s(dn, password)
if manage_user: if manage_user:
result = con.search_s(dn, ldap.SCOPE_BASE, result = con.search_s(dn, ldap.SCOPE_BASE,
"(objectClass=*)", "(objectClass=*)",
[user_firstname_attrib, [user_firstname_attrib, user_lastname_attrib, user_mail_attrib])[0][1]
user_lastname_attrib,
user_mail_attrib])[0][1]
if ldap_mode == 'company': if ldap_mode == 'company':
# no DNs or password needed to search directory # no DNs or password needed to search directory
@@ -319,9 +326,7 @@ def ldap_auth(server='ldap', port=None,
# find the uid # find the uid
attrs = ['uid'] attrs = ['uid']
if manage_user: if manage_user:
attrs.extend([user_firstname_attrib, attrs.extend([user_firstname_attrib, user_lastname_attrib, user_mail_attrib])
user_lastname_attrib,
user_mail_attrib])
# perform the actual search # perform the actual search
company_search_result = con.search_s(ldap_basedn, company_search_result = con.search_s(ldap_basedn,
ldap.SCOPE_SUBTREE, ldap.SCOPE_SUBTREE,
@@ -337,13 +342,11 @@ def ldap_auth(server='ldap', port=None,
basedns = ldap_basedn basedns = ldap_basedn
else: else:
basedns = [ldap_basedn] basedns = [ldap_basedn]
filter = '(&(uid=%s)(%s))' % ( filter = '(&(uid=%s)(%s))' % (ldap.filter.escape_filter_chars(username), filterstr)
ldap.filter.escape_filter_chars(username), filterstr)
found = False found = False
for basedn in basedns: for basedn in basedns:
try: try:
result = con.search_s(basedn, ldap.SCOPE_SUBTREE, result = con.search_s(basedn, ldap.SCOPE_SUBTREE, filter)
filter)
if result: if result:
user_dn = result[0][0] user_dn = result[0][0]
# Check the password # Check the password
@@ -352,9 +355,10 @@ def ldap_auth(server='ldap', port=None,
break break
except ldap.LDAPError, detail: except ldap.LDAPError, detail:
(exc_type, exc_value) = sys.exc_info()[:2] (exc_type, exc_value) = sys.exc_info()[:2]
logger.warning( logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
"ldap_auth: searching %s for %s resulted in %s: %s\n" % filter,
(basedn, filter, exc_type, exc_value) exc_type,
exc_value)
) )
if not found: if not found:
logger.warning('User [%s] not found!' % username) logger.warning('User [%s] not found!' % username)
@@ -367,10 +371,7 @@ def ldap_auth(server='ldap', port=None,
basedns = ldap_basedn basedns = ldap_basedn
else: else:
basedns = [ldap_basedn] basedns = [ldap_basedn]
filter = '(&(%s=%s)(%s))' % (username_attrib, filter = '(&(%s=%s)(%s))' % (username_attrib, ldap.filter.escape_filter_chars(username), filterstr)
ldap.filter.escape_filter_chars(
username),
filterstr)
if custom_scope == 'subtree': if custom_scope == 'subtree':
ldap_scope = ldap.SCOPE_SUBTREE ldap_scope = ldap.SCOPE_SUBTREE
elif custom_scope == 'base': elif custom_scope == 'base':
@@ -389,9 +390,10 @@ def ldap_auth(server='ldap', port=None,
break break
except ldap.LDAPError, detail: except ldap.LDAPError, detail:
(exc_type, exc_value) = sys.exc_info()[:2] (exc_type, exc_value) = sys.exc_info()[:2]
logger.warning( logger.warning("ldap_auth: searching %s for %s resulted in %s: %s\n" % (basedn,
"ldap_auth: searching %s for %s resulted in %s: %s\n" % filter,
(basedn, filter, exc_type, exc_value) exc_type,
exc_value)
) )
if not found: if not found:
logger.warning('User [%s] not found!' % username) logger.warning('User [%s] not found!' % username)
@@ -401,16 +403,14 @@ def ldap_auth(server='ldap', port=None,
logger.info('[%s] Manage user data' % str(username)) logger.info('[%s] Manage user data' % str(username))
try: try:
if user_firstname_part is not None: if user_firstname_part is not None:
store_user_firstname = result[user_firstname_attrib][ store_user_firstname = result[user_firstname_attrib][0].split(' ', 1)[user_firstname_part]
0].split(' ', 1)[user_firstname_part]
else: else:
store_user_firstname = result[user_firstname_attrib][0] store_user_firstname = result[user_firstname_attrib][0]
except KeyError, e: except KeyError, e:
store_user_firstname = None store_user_firstname = None
try: try:
if user_lastname_part is not None: if user_lastname_part is not None:
store_user_lastname = result[user_lastname_attrib][ store_user_lastname = result[user_lastname_attrib][0].split(' ', 1)[user_lastname_part]
0].split(' ', 1)[user_lastname_part]
else: else:
store_user_lastname = result[user_lastname_attrib][0] store_user_lastname = result[user_lastname_attrib][0]
except KeyError, e: except KeyError, e:
@@ -419,32 +419,28 @@ def ldap_auth(server='ldap', port=None,
store_user_mail = result[user_mail_attrib][0] store_user_mail = result[user_mail_attrib][0]
except KeyError, e: except KeyError, e:
store_user_mail = None store_user_mail = None
try: update_or_insert_values = {'first_name': store_user_firstname,
# 'last_name': store_user_lastname,
'email': store_user_mail,
'username': username}
if '@' not in username:
# user as username # user as username
# ################# # ################
fields = ['first_name', 'last_name', 'email']
user_in_db = db(db.auth_user.username == username) user_in_db = db(db.auth_user.username == username)
if user_in_db.count() > 0: elif '@' in username:
user_in_db.update(first_name=store_user_firstname,
last_name=store_user_lastname,
email=store_user_mail)
else:
db.auth_user.insert(first_name=store_user_firstname,
last_name=store_user_lastname,
email=store_user_mail,
username=username)
except:
#
# user as email # user as email
# ############## # #############
fields = ['first_name', 'last_name']
user_in_db = db(db.auth_user.email == username) user_in_db = db(db.auth_user.email == username)
if user_in_db.count() > 0: update_or_insert_values = dict(((f, update_or_insert_values[f]) for f in fields))
user_in_db.update(first_name=store_user_firstname,
last_name=store_user_lastname) if user_in_db.count() > 0:
else: actual_values = user_in_db.select(*[db.auth_user[f] for f in fields]).first().as_dict()
db.auth_user.insert(first_name=store_user_firstname, if update_or_insert_values != actual_values: # We don't update record if values are the same
last_name=store_user_lastname, user_in_db.update(**update_or_insert_values)
email=username) else:
db.auth_user.insert(**update_or_insert_values)
con.unbind() con.unbind()
if manage_groups: if manage_groups:
@@ -486,9 +482,7 @@ def ldap_auth(server='ldap', port=None,
# No match # No match
return False return False
def do_manage_groups(username, def do_manage_groups(username, password=None, db=db):
password=None,
db=db):
""" """
Manage user groups Manage user groups
@@ -508,23 +502,19 @@ def ldap_auth(server='ldap', port=None,
# Get all group name where the user is in actually in local db # Get all group name where the user is in actually in local db
# ############################################################# # #############################################################
try: try:
db_user_id = db(db.auth_user.username == username).select( db_user_id = db(db.auth_user.username == username).select(db.auth_user.id).first().id
db.auth_user.id).first().id
except: except:
try: try:
db_user_id = db(db.auth_user.email == username).select( db_user_id = db(db.auth_user.email == username).select(db.auth_user.id).first().id
db.auth_user.id).first().id
except AttributeError, e: except AttributeError, e:
# #
# There is no user in local db # There is no user in local db
# We create one # We create one
# ############################## # ##############################
try: try:
db_user_id = db.auth_user.insert(username=username, db_user_id = db.auth_user.insert(username=username, first_name=username)
first_name=username)
except AttributeError, e: except AttributeError, e:
db_user_id = db.auth_user.insert(email=username, db_user_id = db.auth_user.insert(email=username, first_name=username)
first_name=username)
if not db_user_id: if not db_user_id:
logging.error( logging.error(
'There is no username or email for %s!' % username) 'There is no username or email for %s!' % username)
@@ -532,27 +522,23 @@ def ldap_auth(server='ldap', port=None,
# if old pydal version, assume this is a relational database which can do joins # if old pydal version, assume this is a relational database which can do joins
db_can_join = db.can_join() if hasattr(db, 'can_join') else True db_can_join = db.can_join() if hasattr(db, 'can_join') else True
if db_can_join: if db_can_join:
db_group_search = db( db_group_search = \
(db.auth_membership.user_id == db_user_id) & db((db.auth_membership.user_id == db_user_id) &
(db.auth_user.id == db.auth_membership.user_id) & (db.auth_user.id == db.auth_membership.user_id) &
(db.auth_group.id == db.auth_membership.group_id)) (db.auth_group.id == db.auth_membership.group_id))
else: else:
# no joins on NoSQL databases, perform two queries # no joins on NoSQL databases, perform two queries
db_group_search = db(db.auth_membership.user_id == db_user_id) db_group_search = db(db.auth_membership.user_id == db_user_id)
group_ids = [x.group_id for x in db_group_search.select( group_ids = [x.group_id for x in db_group_search.select(db.auth_membership.group_id, distinct=True)]
db.auth_membership.group_id, distinct=True)]
db_group_search = db(db.auth_group.id.belongs(group_ids)) db_group_search = db(db.auth_group.id.belongs(group_ids))
db_groups_of_the_user = list() db_groups_of_the_user = list()
db_group_id = dict() db_group_id = dict()
if db_group_search.count() > 0: if db_group_search.count() > 0:
for group in db_group_search.select(db.auth_group.id, for group in db_group_search.select(db.auth_group.id, db.auth_group.role, distinct=True):
db.auth_group.role,
distinct=True):
db_group_id[group.role] = group.id db_group_id[group.role] = group.id
db_groups_of_the_user.append(group.role) db_groups_of_the_user.append(group.role)
logging.debug('db groups of user %s: %s' % logging.debug('db groups of user %s: %s' % (username, str(db_groups_of_the_user)))
(username, str(db_groups_of_the_user)))
# #
# Delete user membership from groups where user is not anymore # Delete user membership from groups where user is not anymore
@@ -560,8 +546,7 @@ def ldap_auth(server='ldap', port=None,
for group_to_del in db_groups_of_the_user: for group_to_del in db_groups_of_the_user:
if ldap_groups_of_the_user.count(group_to_del) == 0: if ldap_groups_of_the_user.count(group_to_del) == 0:
db((db.auth_membership.user_id == db_user_id) & db((db.auth_membership.user_id == db_user_id) &
(db.auth_membership.group_id == \ (db.auth_membership.group_id == db_group_id[group_to_del])).delete()
db_group_id[group_to_del])).delete()
# #
# Create user membership in groups where user is not in already # Create user membership in groups where user is not in already
@@ -569,16 +554,12 @@ def ldap_auth(server='ldap', port=None,
for group_to_add in ldap_groups_of_the_user: for group_to_add in ldap_groups_of_the_user:
if db_groups_of_the_user.count(group_to_add) == 0: if db_groups_of_the_user.count(group_to_add) == 0:
if db(db.auth_group.role == group_to_add).count() == 0: if db(db.auth_group.role == group_to_add).count() == 0:
gid = db.auth_group.insert(role=group_to_add, gid = db.auth_group.insert(role=group_to_add, description='Generated from LDAP')
description='Generated from LDAP')
else: else:
gid = db(db.auth_group.role == group_to_add).select( gid = db(db.auth_group.role == group_to_add).select(db.auth_group.id).first().id
db.auth_group.id).first().id db.auth_membership.insert(user_id=db_user_id, group_id=gid)
db.auth_membership.insert(user_id=db_user_id,
group_id=gid)
except: except:
logger.warning("[%s] Groups are not managed successfully!" % logger.warning("[%s] Groups are not managed successfully!" % str(username))
str(username))
import traceback import traceback
logger.debug(traceback.format_exc()) logger.debug(traceback.format_exc())
return False return False
@@ -669,10 +650,12 @@ def ldap_auth(server='ldap', port=None,
con.simple_bind_s(username, password) con.simple_bind_s(username, password)
logger.debug('Ldap username connect...') logger.debug('Ldap username connect...')
# We have to use the full string # We have to use the full string
username = con.search_ext_s(base_dn, ldap.SCOPE_SUBTREE, username = \
"(&(sAMAccountName=%s)(%s))" % con.search_ext_s(base_dn,
(ldap.filter.escape_filter_chars(username_bare), ldap.SCOPE_SUBTREE,
filterstr), ["cn"])[0][0] "(&(sAMAccountName=%s)(%s))" % (ldap.filter.escape_filter_chars(username_bare),
filterstr),
["cn"])[0][0]
else: else:
if ldap_binddn: if ldap_binddn:
# need to search directory with an bind_dn account 1st # need to search directory with an bind_dn account 1st
@@ -685,18 +668,14 @@ def ldap_auth(server='ldap', port=None,
if username is None: if username is None:
return list() return list()
# search for groups where user is in # search for groups where user is in
filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars( filter = '(&(%s=%s)(%s))' % (ldap.filter.escape_filter_chars(group_member_attrib),
group_member_attrib
),
ldap.filter.escape_filter_chars(username), ldap.filter.escape_filter_chars(username),
group_filterstr) group_filterstr)
group_search_result = con.search_s(group_dn, group_search_result = con.search_s(group_dn, ldap.SCOPE_SUBTREE, filter, [group_name_attrib])
ldap.SCOPE_SUBTREE,
filter, [group_name_attrib])
ldap_groups_of_the_user = list() ldap_groups_of_the_user = list()
for group_row in group_search_result: for group_row in group_search_result:
group = group_row[1] group = group_row[1]
if type(group) == dict and group.has_key(group_name_attrib): if type(group) == dict and group_name_attrib in group:
ldap_groups_of_the_user.extend(group[group_name_attrib]) ldap_groups_of_the_user.extend(group[group_name_attrib])
con.unbind() con.unbind()
+21 -9
View File
@@ -139,24 +139,36 @@ server for requests. It can be used for the optional"scope" parameters for Face
Return the access token generated by the authenticating server. Return the access token generated by the authenticating server.
If token is already in the session that one will be used. If token is already in the session that one will be used.
If token has expired refresh_token is used to get another token.
Otherwise the token is fetched from the auth server. Otherwise the token is fetched from the auth server.
""" """
refresh_token = None
if current.session.token and 'expires' in current.session.token: if current.session.token and 'expires' in current.session.token:
expires = current.session.token['expires'] expires = current.session.token['expires']
# reuse token until expiration # reuse token until expiration
if expires == 0 or expires > time.time(): if expires == 0 or expires > time.time():
return current.session.token['access_token'] return current.session.token['access_token']
if 'refresh_token' in current.session.token:
refresh_token = current.session.token['refresh_token']
code = current.request.vars.code code = current.request.vars.code
if code: if code or refresh_token:
data = dict(client_id=self.client_id, data = dict(
client_secret=self.client_secret, client_id=self.client_id,
redirect_uri=current.session.redirect_uri, client_secret=self.client_secret,
code=code, )
grant_type='authorization_code' if code:
) data.update(
redirect_uri=current.session.redirect_uri,
code=code,
grant_type='authorization_code'
)
elif refresh_token:
data.update(
refresh_token=refresh_token,
grant_type='refresh_token'
)
open_url = None open_url = None
opener = self.__build_url_opener(self.token_url) opener = self.__build_url_opener(self.token_url)
@@ -51,7 +51,7 @@ class OneallAccount(object):
reg_id=profile.get('identity_token','') reg_id=profile.get('identity_token','')
username=profile.get('preferredUsername',email) username=profile.get('preferredUsername',email)
first_name=name.get('givenName', dname.split(' ')[0]) 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, return dict(registration_id=reg_id,username=username,email=email,
first_name=first_name,last_name=last_name) first_name=first_name,last_name=last_name)
self.mappings.default = defaultmapping self.mappings.default = defaultmapping
+53 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf8 -*- # -*- coding: utf-8 -*-
# Plural-Forms for fr (French)) # Plural-Forms for fr (French))
nplurals=2 # French language has 2 forms: nplurals=2 # French language has 2 forms:
@@ -15,3 +15,55 @@ get_plural_id = lambda n: int(n != 1)
# for words (or phrases) not found in plural_dict dictionary # for words (or phrases) not found in plural_dict dictionary
# construct_plural_form = lambda word, plural_id: (word + 'suffix') # construct_plural_form = lambda word, plural_id: (word + 'suffix')
irregular={
'aïeul': 'aïeux',
'bonhomme': 'bonshommes',
'ciel': 'cieux',
'oeil': 'yeux',
'œil': 'yeux',
'madame': 'mesdames',
'mademoiselle': 'mesdemoiselles',
'monsieur': 'messieurs',
'bijou': 'bijoux',
'caillou': 'cailloux',
'chou': 'choux',
'genou': 'genoux',
'hibou': 'hiboux',
'joujou': 'joujoux',
'pou': 'poux',
'corail': ' coraux',
'émail': 'émaux',
'travail': 'travaux',
'vitrail': 'vitraux',
'soupirail': 'soupiraux',
'bail': 'baux',
'fermail': 'fermaux',
'ventail': 'ventaux',
'bleu': 'bleus',
'pneu': 'pneus',
'émeu': 'émeus',
'enfeu': 'enfeus',
#'lieu': 'lieus', # poisson
}
def construct_plural_form(word, plural_id):
u"""
>>> [construct_plural_form(x, 1) for x in \
[ 'bleu', 'nez', 'sex', 'bas', 'gruau', 'jeu', 'journal',\
'chose' ]]
['bleus', 'nez', 'sex', 'bas', 'gruaux', 'jeux', 'journaux', 'choses']
"""
if word in irregular:
return irregular[word]
if word[-1:] in ('s', 'x', 'z'):
return word
if word[-2:] in ('au', 'eu'):
return word + 'x'
if word[-2:] == 'al':
return word[0:-2] + 'aux'
return word + 's'
if __name__ == '__main__':
import doctest
doctest.testmod()
+86 -78
View File
@@ -2,20 +2,20 @@
Developed by niphlod@gmail.com Developed by niphlod@gmail.com
Released under web2py license because includes gluon/cache.py source code 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: try:
import cPickle as pickle import cPickle as pickle
except: except:
import pickle import pickle
import time import time
import re import re
import logging import logging
import thread import thread
import random 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") logger = logging.getLogger("web2py.cache.redis")
@@ -24,20 +24,36 @@ locker = thread.allocate_lock()
def RedisCache(*args, **vars): def RedisCache(*args, **vars):
""" """
Usage example: put in models Usage example: put in models::
from gluon.contrib.redis_cache import RedisCache First of all install Redis
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True, password=None) Ubuntu :
sudo apt-get install redis-server
sudo pip install redis
:param db: redis db to use (0..16) Then
:param debug: if True adds to stats() the total_hits and misses
:param with_lock: sets the default locking mode for creating new keys. 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 By default is False (usualy when you choose Redis you do it
for performances reason) for performances reason)
When True, only one thread/process can set a value concurrently 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 to enforce locking. The with_lock parameter overrides the one set in the
cache.redis instance creation cache.redis instance creation
@@ -81,22 +97,19 @@ class RedisClient(object):
MAX_RETRIES = 5 MAX_RETRIES = 5
RETRIES = 0 RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False, password=None): def __init__(self, redis_conn=None, debug=False,
self.server = server with_lock=False, fail_gracefully=False):
self.password = password
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
self.request = current.request self.request = current.request
self.debug = debug self.debug = debug
self.with_lock = with_lock 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: if self.request:
app = self.request.application app = self.request.application
else: else:
app = '' app = ''
if not app in self.meta_storage: if app not in self.meta_storage:
self.storage = self.meta_storage[app] = { self.storage = self.meta_storage[app] = {
CacheAbstract.cache_stats_name: { CacheAbstract.cache_stats_name: {
'hit_total': 0, 'hit_total': 0,
@@ -105,9 +118,10 @@ class RedisClient(object):
else: else:
self.storage = self.meta_storage[app] 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): def initialize(self):
pass pass
@@ -121,90 +135,86 @@ class RedisClient(object):
value = None value = None
ttl = 0 ttl = 0
try: try:
#is there a value # is there a value
obj = self.r_server.get(newKey) obj = self.r_server.get(newKey)
#what's its ttl # what's its ttl
if obj: if obj:
ttl = self.r_server.ttl(newKey) ttl = self.r_server.ttl(newKey)
if ttl > time_expire: if ttl > time_expire:
obj = None obj = None
if obj: if obj:
#was cached # was cached
if self.debug: if self.debug:
self.r_server.incr('web2py_cache_statistics:hit_total') self.r_server.incr('web2py_cache_statistics:hit_total')
value = pickle.loads(obj) value = pickle.loads(obj)
elif f is None: elif f is None:
#delete and never look back # delete and never look back
self.r_server.delete(newKey) self.r_server.delete(newKey)
else: else:
#naive distributed locking # naive distributed locking
if with_lock: if with_lock:
lock_key = '%s:__lock' % newKey lock_key = '%s:__lock' % newKey
try: randomvalue = time.time()
while True: al = acquire_lock(self.r_server, lock_key, randomvalue)
lock = self.r_server.setnx(lock_key, 1) # someone may have computed it
if lock: obj = self.r_server.get(newKey)
value = self.cache_it(newKey, f, time_expire) if obj is None:
break value = self.cache_it(newKey, f, time_expire)
else: else:
time.sleep(0.2) value = pickle.loads(obj)
#did someone else create it in the meanwhile ? release_lock(self, lock_key, al)
obj = self.r_server.get(newKey)
if obj:
value = pickle.loads(obj)
break
finally:
self.r_server.delete(lock_key)
else: else:
#without distributed locking # without distributed locking
value = self.cache_it(newKey, f, time_expire) value = self.cache_it(newKey, f, time_expire)
return value return value
except ConnectionError: except RConnectionError:
return self.retry_call(key, f, time_expire, with_lock) return self.retry_call(key, f, time_expire, with_lock)
def cache_it(self, key, f, time_expire): def cache_it(self, key, f, time_expire):
if self.debug: if self.debug:
self.r_server.incr('web2py_cache_statistics:misses') self.r_server.incr('web2py_cache_statistics:misses')
cache_set_key = self.cache_set_key cache_set_key = self.cache_set_key
expireat = int(time.time() + time_expire) + 120 expire_at = int(time.time() + time_expire) + 120
bucket_key = "%s:%s" % (cache_set_key, expireat / 60) bucket_key = "%s:%s" % (cache_set_key, expire_at / 60)
value = f() value = f()
value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) value_ = pickle.dumps(value, pickle.HIGHEST_PROTOCOL)
if time_expire == 0: if time_expire == 0:
time_expire = 1 time_expire = 1
self.r_server.setex(key, value_, time_expire) self.r_server.setex(key, time_expire, value_)
#print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat)) # 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(((expireat/60) + 1)*60)) # print 'that will expire on %s' % (bucket_key, time.ctime(((expire_at / 60) + 1) * 60))
p = self.r_server.pipeline() p = self.r_server.pipeline()
#add bucket to the fixed set # add bucket to the fixed set
p.sadd(cache_set_key, bucket_key) p.sadd(cache_set_key, bucket_key)
#sets the key # sets the key
p.setex(key, value_, time_expire) p.setex(key, time_expire, value_)
#add the key to the bucket # add the key to the bucket
p.sadd(bucket_key, key) p.sadd(bucket_key, key)
#expire the bucket properly # expire the bucket properly
p.expireat(bucket_key, ((expireat/60) + 1)*60) p.expireat(bucket_key, ((expire_at / 60) + 1) * 60)
p.execute() p.execute()
return value 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 self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES: if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping %s seconds before reconnecting" % logger.error("sleeping %s seconds before reconnecting" % (2 * self.RETRIES))
(2 * self.RETRIES))
time.sleep(2 * self.RETRIES) time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug, self.with_lock) if self.fail_gracefully:
return self.__call__(key, f, time_expire, with_locking) self.RETRIES = 0
return f()
return self.__call__(key, f, time_expire, with_lock)
else: else:
self.RETRIES = 0 self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % ( if self.fail_gracefully:
self.server)) return f
raise RConnectionError('Redis instance is unavailable')
def increment(self, key, value=1): def increment(self, key, value=1):
try: try:
newKey = self.__keyFormat__(key) newKey = self.__keyFormat__(key)
return self.r_server.incr(newKey, value) return self.r_server.incr(newKey, value)
except ConnectionError: except RConnectionError:
return self.retry_increment(key, value) return self.retry_increment(key, value)
def retry_increment(self, key, value): def retry_increment(self, key, value):
@@ -212,12 +222,10 @@ class RedisClient(object):
if self.RETRIES <= self.MAX_RETRIES: if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping some seconds before reconnecting") logger.error("sleeping some seconds before reconnecting")
time.sleep(2 * self.RETRIES) time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.increment(key, value) return self.increment(key, value)
else: else:
self.RETRIES = 0 self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % ( raise RConnectionError('Redis instance is unavailable')
self.server))
def clear(self, regex): def clear(self, regex):
""" """
@@ -225,9 +233,9 @@ class RedisClient(object):
clear cache entries clear cache entries
""" """
r = re.compile(regex) r = re.compile(regex)
#get all buckets # get all buckets
buckets = self.r_server.smembers(self.cache_set_key) buckets = self.r_server.smembers(self.cache_set_key)
#get all keys in buckets # get all keys in buckets
if buckets: if buckets:
keys = self.r_server.sunion(buckets) keys = self.r_server.sunion(buckets)
else: else:
@@ -237,8 +245,8 @@ class RedisClient(object):
for a in keys: for a in keys:
if r.match(str(a).replace(prefix, '', 1)): if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a) pipe.delete(a)
if random.randrange(0,100) < 10: if random.randrange(0, 100) < 10:
#do this just once in a while (10% chance) # do this just once in a while (10% chance)
self.clear_buckets(buckets) self.clear_buckets(buckets)
pipe.execute() pipe.execute()
@@ -254,19 +262,19 @@ class RedisClient(object):
return self.r_server.delete(newKey) return self.r_server.delete(newKey)
def stats(self): def stats(self):
statscollector = self.r_server.info() stats_collector = self.r_server.info()
if self.debug: if self.debug:
statscollector['w2p_stats'] = dict( stats_collector['w2p_stats'] = dict(
hit_total=self.r_server.get( hit_total=self.r_server.get(
'web2py_cache_statistics:hit_total'), 'web2py_cache_statistics:hit_total'),
misses=self.r_server.get('web2py_cache_statistics:misses') 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:*" % ( for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)): self.request.application)):
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a) stats_collector['w2p_keys']["%s_expire_in_sec" % a] = self.r_server.ttl(a)
return statscollector return stats_collector
def __keyFormat__(self, key): def __keyFormat__(self, key):
return '%s%s' % (self.prefix, key.replace(' ', '_')) return '%s%s' % (self.prefix, key.replace(' ', '_'))
+785
View File
@@ -0,0 +1,785 @@
#!/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 (%s)' % self.w_stats.status)
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:
next_run_time = task.start_time + datetime.timedelta(
seconds=task.period * times_run
)
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=db.scheduler_task.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
+43 -73
View File
@@ -1,13 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
Developed by niphlod@gmail.com 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 logging
import thread 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") logger = logging.getLogger("web2py.session.redis")
@@ -16,10 +21,20 @@ locker = thread.allocate_lock()
def RedisSession(*args, **vars): def RedisSession(*args, **vars):
""" """
Usage example: put in models Usage example: put in models::
from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False, password=None) from gluon.contrib.redis_utils import RConn
session.connect(request, response, db = sessiondb) rconn = RConn()
from gluon.contrib.redis_session
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 Simple slip-in storage for session
""" """
@@ -36,30 +51,9 @@ def RedisSession(*args, **vars):
class RedisClient(object): class RedisClient(object):
meta_storage = {} def __init__(self, redis_conn, session_expiry=False, with_lock=False):
MAX_RETRIES = 5 self.r_server = redis_conn
RETRIES = 0 self._release_script = register_release_lock(self.r_server)
_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)
self.tablename = None self.tablename = None
self.session_expiry = session_expiry self.session_expiry = session_expiry
self.with_lock = with_lock self.with_lock = with_lock
@@ -93,12 +87,11 @@ class RedisClient(object):
class MockTable(object): class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False): def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
# here self.db is the RedisClient instance
self.db = db self.db = db
self.r_server = r_server
self.tablename = tablename self.tablename = tablename
# set the namespace for sessions of this app # set the namespace for sessions of this app
self.keyprefix = 'w2p:sess:%s' % tablename.replace( self.keyprefix = 'w2p:sess:%s' % tablename.replace('web2py_session_', '')
'web2py_session_', '')
# fast auto-increment id (needed for session handling) # fast auto-increment id (needed for session handling)
self.serial = "%s:serial" % self.keyprefix self.serial = "%s:serial" % self.keyprefix
# index of all the session keys of this app # index of all the session keys of this app
@@ -126,7 +119,7 @@ class MockTable(object):
if key == 'id': if key == 'id':
# return a fake query. We need to query it just by id for normal operations # return a fake query. We need to query it just by id for normal operations
self.query = MockQuery( self.query = MockQuery(
field='id', db=self.r_server, field='id', db=self.db,
prefix=self.keyprefix, session_expiry=self.session_expiry, prefix=self.keyprefix, session_expiry=self.session_expiry,
with_lock=self.with_lock, unique_key=self.unique_key with_lock=self.with_lock, unique_key=self.unique_key
) )
@@ -140,12 +133,12 @@ class MockTable(object):
# 'locked', 'client_ip','created_datetime','modified_datetime' # 'locked', 'client_ip','created_datetime','modified_datetime'
# 'unique_key', 'session_data' # 'unique_key', 'session_data'
# retrieve a new key # 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 key = self.keyprefix + ':' + newid
if self.with_lock: if self.with_lock:
key_lock = key + ':lock' key_lock = key + ':lock'
acquire_lock(self.r_server, key_lock, newid) acquire_lock(self.db.r_server, key_lock, newid)
with self.r_server.pipeline() as pipe: with self.db.r_server.pipeline() as pipe:
# add it to the index # add it to the index
pipe.sadd(self.id_idx, key) pipe.sadd(self.id_idx, key)
# set a hash key with the Storage # set a hash key with the Storage
@@ -154,7 +147,7 @@ class MockTable(object):
pipe.expire(key, self.session_expiry) pipe.expire(key, self.session_expiry)
pipe.execute() pipe.execute()
if self.with_lock: if self.with_lock:
release_lock(self.r_server, key_lock, newid) release_lock(self.db, key_lock, newid)
return newid return newid
@@ -186,8 +179,8 @@ class MockQuery(object):
# means that someone wants to retrieve the key self.value # means that someone wants to retrieve the key self.value
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
if self.with_lock: if self.with_lock:
acquire_lock(self.db, key + ':lock', self.value) acquire_lock(self.db.r_server, key + ':lock', self.value, 2)
rtn = self.db.hgetall(key) rtn = self.db.r_server.hgetall(key)
if rtn: if rtn:
if self.unique_key: if self.unique_key:
# make sure the id and unique_key are correct # make sure the id and unique_key are correct
@@ -201,13 +194,13 @@ class MockQuery(object):
rtn = [] rtn = []
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
# find all session keys of this app # 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: for sess in allkeys:
val = self.db.hgetall(sess) val = self.db.r_server.hgetall(sess)
if not val: if not val:
if self.session_expiry: if self.session_expiry:
# clean up the idx, because the key expired # clean up the idx, because the key expired
self.db.srem(id_idx, sess) self.db.r_server.srem(id_idx, sess)
continue continue
val = Storage(val) val = Storage(val)
# add a delete_record method (necessary for sessions2trash.py) # add a delete_record method (necessary for sessions2trash.py)
@@ -222,9 +215,9 @@ class MockQuery(object):
# means that the session has been found and needs an update # means that the session has been found and needs an update
if self.op == 'eq' and self.field == 'id' and self.value: if self.op == 'eq' and self.field == 'id' and self.value:
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
if not self.db.exists(key): if not self.db.r_server.exists(key):
return None return None
with self.db.pipeline() as pipe: with self.db.r_server.pipeline() as pipe:
pipe.hmset(key, kwargs) pipe.hmset(key, kwargs)
if self.session_expiry: if self.session_expiry:
pipe.expire(key, self.session_expiry) pipe.expire(key, self.session_expiry)
@@ -238,7 +231,7 @@ class MockQuery(object):
if self.op == 'eq' and self.field == 'id' and self.value: if self.op == 'eq' and self.field == 'id' and self.value:
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
key = self.keyprefix + ':' + str(self.value) key = self.keyprefix + ':' + str(self.value)
with self.db.pipeline() as pipe: with self.db.r_server.pipeline() as pipe:
pipe.delete(key) pipe.delete(key)
pipe.srem(id_idx, key) pipe.srem(id_idx, key)
rtn = pipe.execute() rtn = pipe.execute()
@@ -254,29 +247,6 @@ class RecordDeleter(object):
def __call__(self): def __call__(self):
id_idx = "%s:id_idx" % self.keyprefix id_idx = "%s:id_idx" % self.keyprefix
# remove from the index # remove from the index
self.db.srem(id_idx, self.key) self.db.r_server.srem(id_idx, self.key)
# remove the key itself # remove the key itself
self.db.delete(self.key) self.db.r_server.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)
+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
+2 -2
View File
@@ -33,8 +33,8 @@ except ImportError:
class JSONRPCError(RuntimeError): class JSONRPCError(RuntimeError):
"Error object for remote procedure call fail" "Error object for remote procedure call fail"
def __init__(self, code, message, data=None): def __init__(self, code, message, data=''):
value = "%s: %s\n%s" % (code, message, '\n'.join(data or '')) value = "%s: %s\n%s" % (code, message, '\n'.join(data))
RuntimeError.__init__(self, value) RuntimeError.__init__(self, value)
self.code = code self.code = code
self.message = message self.message = message
+30 -8
View File
@@ -16,7 +16,7 @@ def quote(text):
class Node: class Node:
def __init__(self, name, value, url='.', readonly=False, active=True, def __init__(self, name, value, url='.', readonly=False, active=True,
onchange=None, **kwarg): onchange=None, select=False, size=4, **kwarg):
self.url = url self.url = url
self.name = name self.name = name
self.value = str(value) self.value = str(value)
@@ -26,11 +26,21 @@ class Node:
self.readonly = readonly self.readonly = readonly
self.active = active self.active = active
self.onchange = onchange self.onchange = onchange
self.size = 4 self.size = size
self.locked = False self.locked = False
self.select = value if select and not isinstance(value, str) else False
def xml(self): def xml(self):
return """<input name="%s" id="%s" value="%s" size="%s" if self.select:
selectAttributes = dict(_name=self.name,_id=self.name,_size=self.size,
_onblur="ajax('%s/blur',['%s']);"%(self.url,self.name))
# _onkeyup="ajax('%s/keyup',['%s'], ':eval');"%(self.url,self.name),
# _onfocus="ajax('%s/focus',['%s'], ':eval');"%(self.url,self.name),
for k,v in selectAttributes.items():
self.select[k] = v
return self.select.xml()
else:
return """<input name="%s" id="%s" value="%s" size="%s"
onkeyup="ajax('%s/keyup',['%s'], ':eval');" onkeyup="ajax('%s/keyup',['%s'], ':eval');"
onfocus="ajax('%s/focus',['%s'], ':eval');" onfocus="ajax('%s/focus',['%s'], ':eval');"
onblur="ajax('%s/blur',['%s'], ':eval');" %s/> onblur="ajax('%s/blur',['%s'], ':eval');" %s/>
@@ -391,7 +401,8 @@ class Sheet:
def __init__(self, rows, cols, url='.', readonly=False, def __init__(self, rows, cols, url='.', readonly=False,
active=True, onchange=None, value=None, data=None, active=True, onchange=None, value=None, data=None,
headers=None, update_button="", **kwarg): headers=None, update_button="", c_headers=None,
r_headers=None, **kwarg):
""" """
Arguments: Arguments:
@@ -425,6 +436,9 @@ class Sheet:
self.tr_attributes = {} self.tr_attributes = {}
self.td_attributes = {} self.td_attributes = {}
self.c_headers = c_headers
self.r_headers = r_headers
self.data = data self.data = data
self.readonly = readonly self.readonly = readonly
@@ -505,7 +519,7 @@ class Sheet:
self.environment[name] = obj self.environment[name] = obj
def cell(self, key, value, readonly=False, active=True, def cell(self, key, value, readonly=False, active=True,
onchange=None, **kwarg): onchange=None, select=False, **kwarg):
""" """
key is the name of the cell key is the name of the cell
value is the initial value of the cell. It can be a formula "=1+3" value is the initial value of the cell. It can be a formula "=1+3"
@@ -528,7 +542,7 @@ class Sheet:
value = value(r, c) value = value(r, c)
node = Node(key, value, self.url, readonly, active, node = Node(key, value, self.url, readonly, active,
onchange, **kwarg) onchange, select=select, **kwarg)
self.nodes[key] = node self.nodes[key] = node
self[key] = value self[key] = value
@@ -781,11 +795,19 @@ class Sheet:
gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT) gluon.html.TH, gluon.html.BR, gluon.html.SCRIPT)
regex = re.compile('r\d+c\d+') regex = re.compile('r\d+c\d+')
header = TR(TH(), *[TH('c%s' % c) if not self.c_headers:
header = TR(TH(), *[TH('c%s' % c)
for c in range(self.cols)]) for c in range(self.cols)])
else:
header = TR(TH(), *[TH('%s' % c)
for c in self.c_headers])
rows = [] rows = []
for r in range(self.rows): for r in range(self.rows):
tds = [TH('r%s' % r), ] if not self.r_headers:
tds = [TH('r%s' % r), ]
else:
tds = [TH('%s' % self.r_headers[r]), ]
for c in range(self.cols): for c in range(self.cols):
key = 'r%sc%s' % (r, c) key = 'r%sc%s' % (r, c)
attributes = {"_class": "w2p_spreadsheet_col_%s" % attributes = {"_class": "w2p_spreadsheet_col_%s" %
+2 -2
View File
@@ -146,8 +146,8 @@ class TokenHandler(tornado.web.RequestHandler):
class DistributeHandler(tornado.websocket.WebSocketHandler): class DistributeHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin): def check_origin(self, origin):
return True return True
def open(self, params): def open(self, params):
group, token, name = params.split('/') + [None, None] group, token, name = params.split('/') + [None, None]
+2 -2
View File
@@ -41,7 +41,7 @@ class CustomImportException(ImportError):
def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1): def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
""" """
web2py's custom importer. It behaves like the standard Python importer but web2py's custom importer. It behaves like the standard Python importer but
it tries to transform import statements as something like it tries to transform import statements as something like
"import applications.app_name.modules.x". "import applications.app_name.modules.x".
If the import fails, it falls back on naive_importer If the import fails, it falls back on naive_importer
@@ -80,7 +80,7 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
if not fromlist: if not fromlist:
# import like "import x" or "import x.y" # import like "import x" or "import x.y"
result = None result = None
for itemname in name.split("."): for itemname in name.split("."):
new_mod = base_importer( new_mod = base_importer(
modules_prefix, globals, locals, [itemname], level) modules_prefix, globals, locals, [itemname], level)
try: try:
+29 -12
View File
@@ -208,7 +208,7 @@ class Request(Storage):
def parse_get_vars(self): def parse_get_vars(self):
"""Takes the QUERY_STRING and unpacks it to get_vars """Takes the QUERY_STRING and unpacks it to get_vars
""" """
query_string = self.env.get('QUERY_STRING', '') query_string = self.env.get('query_string', '')
dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs dget = urlparse.parse_qs(query_string, keep_blank_values=1) # Ref: https://docs.python.org/2/library/cgi.html#cgi.parse_qs
get_vars = self._get_vars = Storage(dget) get_vars = self._get_vars = Storage(dget)
for (key, value) in get_vars.iteritems(): for (key, value) in get_vars.iteritems():
@@ -362,20 +362,30 @@ class Request(Storage):
redirect(URL(scheme='https', args=self.args, vars=self.vars)) redirect(URL(scheme='https', args=self.args, vars=self.vars))
def restful(self): def restful(self):
def wrapper(action, self=self): def wrapper(action, request=self):
def f(_action=action, _self=self, *a, **b): def f(_action=action, *a, **b):
self.is_restful = True request.is_restful = True
method = _self.env.request_method env = request.env
if len(_self.args) and '.' in _self.args[-1]: is_json = env.content_type=='application/json'
_self.args[-1], _, self.extension = self.args[-1].rpartition('.') 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'] = \ current.response.headers['Content-Type'] = \
contenttype('.' + _self.extension.lower()) contenttype('.' + request.extension.lower())
rest_action = _action().get(method, None) rest_action = _action().get(method, None)
if not (rest_action and method == method.upper() if not (rest_action and method == method.upper()
and callable(rest_action)): and callable(rest_action)):
raise HTTP(405, "method not allowed") raise HTTP(405, "method not allowed")
try: try:
return rest_action(*_self.args, **getattr(_self, 'vars', {})) vars = request.vars
if method == 'POST' and is_json:
body = request.body.read()
if len(body):
vars = sj.loads(body)
res = rest_action(*request.args, **vars)
if is_json and not isinstance(res, str):
res = json(res)
return res
except TypeError, e: except TypeError, e:
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
if len(traceback.extract_tb(exc_traceback)) == 1: if len(traceback.extract_tb(exc_traceback)) == 1:
@@ -1023,10 +1033,16 @@ class Session(Storage):
def _fixup_before_save(self): def _fixup_before_save(self):
response = current.response response = current.response
rcookies = response.cookies rcookies = response.cookies
if self._forget and response.session_id_name in rcookies: scookies = rcookies.get(response.session_id_name)
if not scookies:
return
if self._forget:
del rcookies[response.session_id_name] del rcookies[response.session_id_name]
elif self._secure and response.session_id_name in rcookies: return
rcookies[response.session_id_name]['secure'] = True if self.get('httponly_cookies',True):
scookies['HttpOnly'] = True
if self._secure:
scookies['secure'] = True
def clear_session_cookies(self): def clear_session_cookies(self):
request = current.request request = current.request
@@ -1074,6 +1090,7 @@ class Session(Storage):
if response.session_storage_type == 'file': if response.session_storage_type == 'file':
target = recfile.generate(response.session_filename) target = recfile.generate(response.session_filename)
try: try:
self._close(response)
os.unlink(target) os.unlink(target)
except: except:
pass pass
+8 -3
View File
@@ -668,7 +668,7 @@ class XML(XmlComponent):
def XML_unpickle(data): def XML_unpickle(data):
return marshal.loads(data) return XML(marshal.loads(data))
def XML_pickle(data): def XML_pickle(data):
@@ -784,6 +784,9 @@ class DIV(XmlComponent):
else: else:
return self.components[i] return self.components[i]
def get(self, i):
return self.attributes.get(i)
def __setitem__(self, i, value): def __setitem__(self, i, value):
""" """
Sets attribute with name 'i' or component #i. Sets attribute with name 'i' or component #i.
@@ -1135,7 +1138,7 @@ class DIV(XmlComponent):
for (key, value) in kargs.iteritems(): for (key, value) in kargs.iteritems():
if key not in ['first_only', 'replace', 'find_text']: if key not in ['first_only', 'replace', 'find_text']:
if isinstance(value, (str, int)): if isinstance(value, (str, int)):
if self[key] != str(value): if str(self[key]) != str(value):
check = False check = False
elif key in self.attributes: elif key in self.attributes:
if not value.search(str(self[key])): if not value.search(str(self[key])):
@@ -1856,6 +1859,8 @@ class INPUT(DIV):
try: try:
(value, errors) = validator(value) (value, errors) = validator(value)
except: except:
import traceback
print traceback.format_exc()
msg = "Validation error, field:%s %s" % (name,validator) msg = "Validation error, field:%s %s" % (name,validator)
raise Exception(msg) raise Exception(msg)
if not errors is None: if not errors is None:
@@ -2643,7 +2648,7 @@ def test():
>>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$'))) >>> form=FORM(INPUT(value="Hello World", _name="var", requires=IS_MATCH('^\w+$')))
>>> isinstance(form.as_dict(), dict) >>> isinstance(form.as_dict(), dict)
True True
>>> form.as_dict(flat=True).has_key("vars") >>> "vars" in form.as_dict(flat=True)
True True
>>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0 >>> isinstance(form.as_json(), basestring) and len(form.as_json(sanitize=False)) > 0
True True
+9 -6
View File
@@ -370,8 +370,8 @@ def wsgibase(environ, responder):
cid = env.http_web2py_component_element, cid = env.http_web2py_component_element,
is_local = (env.remote_addr in local_hosts and is_local = (env.remote_addr in local_hosts and
client == env.remote_addr), client == env.remote_addr),
is_shell = cmd_opts and cmd_opts.shell, is_shell = False,
is_sheduler = cmd_opts and cmd_opts.scheduler, is_scheduler = False,
is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \ is_https = env.wsgi_url_scheme in HTTPS_SCHEMES or \
request.env.http_x_forwarded_proto in HTTPS_SCHEMES \ request.env.http_x_forwarded_proto in HTTPS_SCHEMES \
or env.https == 'on' or env.https == 'on'
@@ -423,10 +423,13 @@ def wsgibase(environ, responder):
# ################################################## # ##################################################
if env.http_cookie: if env.http_cookie:
try: for single_cookie in env.http_cookie.split(';'):
request.cookies.load(env.http_cookie) single_cookie = single_cookie.strip()
except Cookie.CookieError, e: if single_cookie:
pass # invalid cookies try:
request.cookies.load(single_cookie)
except Cookie.CookieError:
pass # single invalid cookie ignore
# ################################################## # ##################################################
# try load session or create new session file # try load session or create new session file

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