Compare commits

..

159 Commits

Author SHA1 Message Date
mdipierro 9b1b9e73c5 fixed type in dal with simplejson, thanks Mart 2013-05-25 10:39:16 -05:00
mdipierro 0fc60eedfb R-2.7.4 2013-05-24 12:37:43 -05:00
mdipierro 9e19833421 Merge branch 'master' of github.com:web2py/web2py 2013-05-24 11:44:52 -05:00
mdipierro 31318340e2 Merge pull request #97 from drnextgis/master
Use T operator fot title attribute of grid buttons
2013-05-24 09:44:43 -07:00
mdipierro 6cef27e69c fixed display of version 2013-05-24 11:44:40 -05:00
mdipierro 4f6ab86491 fixed Issue 1500:MySQL driver unable to handle IntegrityError, thanks wheresgatsby 2013-05-24 08:35:09 -05:00
mdipierro 4147867098 better handling of delimeters in template, thanks Corne Dickens 2013-05-24 08:28:45 -05:00
Denis Rykov a0dead1586 Use T operator fot title attribute of grid buttons 2013-05-23 17:58:43 +07:00
mdipierro 0afc18fd24 reverted HTTP(status_message) because redundant 2013-05-19 22:14:43 -05:00
mdipierro 9f695c783e Merge pull request #96 from rpedroso/redis-session
lock in redis session
2013-05-19 17:12:53 -07:00
Ricardo Pedroso 15263ea1d6 add missing client parameter to lua script 2013-05-18 22:26:35 +01:00
Ricardo Pedroso 693c5cd5e4 add an optional lock mechanism to redis sessions 2013-05-18 20:36:53 +01:00
Ricardo Pedroso cc3f045efa get rid of val == dict() 2013-05-18 20:36:53 +01:00
mdipierro 43806a26e7 grid(...,client_side_delete=True) for redirect(client_size=True) 2013-05-17 14:53:04 -05:00
mdipierro cb4c4b3eb5 Merge pull request #95 from niphlod/issue/1380
fix small error in parse_boolean
2013-05-17 12:34:44 -07:00
mdipierro 39558267b9 Merge pull request #94 from niphlod/feature/redis_cache
new and improved redis_cache
2013-05-17 12:33:43 -07:00
niphlod 59a3c5a6e6 fix small error in parse_boolean 2013-05-17 21:23:24 +02:00
mdipierro 2c9eeccc0e fixed issue 1496:Delete button in SQLFORM.grid does not cause browser redirect, thanks jppmssaraiva 2013-05-17 14:19:51 -05:00
mdipierro 7576d85dba ics(...,calname=False) 2013-05-17 13:53:32 -05:00
mdipierro 579f0d752f stripe.charge(token=...) 2013-05-16 22:03:20 -05:00
mdipierro 65e9cc2127 alternative escaping of password in DAL uri 2013-05-15 21:35:17 -05:00
niphlod dc2fd49ecb new and improved redis_cache. Got distributed locking and faster clear() function.
also, the .increment() was a little bit bugged.
2013-05-16 00:44:24 +02:00
mdipierro a380ae69d5 IS_SLUG patch, thanks Jonathan, Niphlod and Villas 2013-05-15 12:41:28 -05:00
mdipierro 0b84911d38 allow tables with no primary keys 2013-05-15 11:20:30 -05:00
mdipierro e64c556f84 Merge pull request #93 from rpedroso/memcache-double
double -> float
2013-05-14 10:06:24 -07:00
mdipierro 18644e027e Merge pull request #92 from rpedroso/fcgi_typo
typo in contrib/gateways/fcgi.py
2013-05-14 10:05:35 -07:00
mdipierro 65642c029c Merge pull request #91 from rpedroso/redis-session
redis_session.py small fixes
2013-05-14 10:04:58 -07:00
mdipierro 11540a6132 Merge pull request #90 from rpedroso/dal-typo
dal typo
2013-05-14 10:04:14 -07:00
mdipierro a6e728316b Merge pull request #89 from espern/french-translation
new french translation + fixed errors in existing translations
2013-05-14 10:03:24 -07:00
Massimo 4cb5e3c674 merged 2013-05-14 12:01:45 -05:00
mdipierro d918a6b2e2 fixed a minor problem with the sql filter 2013-05-14 00:30:05 -05:00
Ricardo Pedroso d721451d15 double -> float 2013-05-14 00:40:50 +01:00
Ricardo Pedroso c5aaebd4a8 typo 2013-05-14 00:15:49 +01:00
Ricardo Pedroso 4c1dbf4e40 one redis instance per application 2013-05-13 21:59:04 +01:00
Ricardo Pedroso c20c055788 typo: self.session.expiry -> self.session_expiry 2013-05-13 21:31:06 +01:00
Ricardo Pedroso 44c13f9fa3 fix missing variable 2013-05-13 21:29:37 +01:00
Ricardo Pedroso ae44a9e0ac dal typo 2013-05-13 20:55:55 +01:00
mdipierro 97ba47f339 allow tables without no id and no primary key 2013-05-13 10:12:09 -05:00
mdipierro 8671b897d0 logout after delete user from profile 2013-05-12 20:08:01 -05:00
mdipierro 1a3361a1bb auth.settings.allow_delete_accounts=True 2013-05-12 10:28:08 -05:00
mdipierro 9ac438d05a removed the timeout issues Issue 1488:Web2py not run OpenSUSE 12.3 64Bits but it may now work as intended 2013-05-11 22:11:21 -05:00
mdipierro 18e45ab994 Issue 1489:some improvement in Mail send class, thanks Alan 2013-05-11 22:03:05 -05:00
mdipierro f00090846e fixed some dropbox issues, thanks Jesus 2013-05-10 08:32:51 -05:00
mdipierro 6590de030c Issue 1481:error using do_connect=False parameter in DAL creation with mongodb and couchdb is fixed, thanks Alan 2013-05-10 08:28:52 -05:00
mdipierro 714fcc5202 Better german trsnalation of admin, thanks Klaus Kappel 2013-05-10 08:26:12 -05:00
mdipierro becc947556 hack for vertica timestamp field 2013-05-09 22:31:34 -05:00
Massimo d547a955ac actual_name for readonly table support 2013-05-09 13:10:48 -05:00
mdipierro 97e0736d8e added some code to help debug issue 1471 2013-05-08 16:52:59 -05:00
mdipierro 20db6daa65 patch moves flash to left avoiding submenu, thanks Jesus 2013-05-08 16:44:52 -05:00
mdipierro 3aefb5f6de es.py thanks Jesus Alvaro 2013-05-08 16:42:29 -05:00
Massimo 30818cdf5e Issue 1482:Patch for /gluon/contrib/login_methods/dropbox_account.py, thanks Jesus 2013-05-07 15:48:59 -05:00
Massimo 4089cf2145 Issue 1480:Patch for /gluon/tools.py, thanks Jesus Alvaro 2013-05-07 15:46:23 -05:00
l01c d7893c5292 new french translation + fixed errors in existing translations 2013-05-07 13:54:39 +02:00
mdipierro cdd3f6ed00 allow arbitrary username to enable register and sign in with facebook, thanks Ting-Yu Chou 2013-05-06 22:49:04 -05:00
mdipierro e44254346c reverting previous patch, Anthony is right, the patch is not backward compatible 2013-05-06 19:49:57 -05:00
mdipierro adf82c1267 fixed Issue 1478:field.uploadfolder instead of request.folder in accepts (sqlhtml.py), thanks szunny 2013-05-06 19:10:12 -05:00
mdipierro 4d98b72702 auth.settings.client_side, thanks Anthony 2013-05-06 18:37:26 -05:00
mdipierro f02240acd3 support for different representations of IPv4 in IPv6, thanks Jonathan 2013-05-06 07:33:54 +10:00
mdipierro cabef57851 improved ipv6 address checked, thanks Jonathan 2013-05-06 07:33:54 +10:00
mdipierro 98ad12a06c db(query)(other if cond else None).select() 2013-05-06 07:33:54 +10:00
mdipierro d454eb2fe7 removed unwated file 2013-05-06 07:33:54 +10:00
mdipierro 0facbe04ef support for different representations of IPv4 in IPv6, thanks Jonathan 2013-05-05 13:31:06 -05:00
mdipierro 8f9f4aae32 improved ipv6 address checked, thanks Jonathan 2013-05-05 10:13:26 -05:00
mdipierro d5bfaf83ee db(query)(other if cond else None).select() 2013-05-04 10:24:26 -05:00
mdipierro 9e46c3cbc4 removed unwated file 2013-05-04 10:11:55 -05:00
Tim Richardson d431206e30 Fixed a very minor typo 2013-05-04 22:13:15 +10:00
mdipierro 6adfb8b944 added setup-web2py-nginx-uwsgi-centos64.sh, thanks jniltinho 2013-05-03 22:25:27 -05:00
mdipierro 0c8ccaac9a Merge pull request #87 from michele-comitini/master
fix oauth issues if redirecting in a form | http custom status error message
2013-05-03 20:22:20 -07:00
mdipierro 4f0ee4543b fixed a problem with hiding passwords for uri = list of uris, thanks David 2013-05-03 22:20:05 -05:00
mdipierro 1383d13334 @db.table.add_method.name, thanks Vinicius 2013-05-03 22:15:01 -05:00
mdipierro 1b260c4b84 fixed typo AF_NET 2013-05-03 21:47:51 -05:00
mdipierro 0ff586a1ca removed file that should not be there, thanks Niphlod 2013-05-03 21:46:06 -05:00
mdipierro 95adb233f7 issue Issue 1474 attachment: addrinfo.patch, thanks Jonathan 2013-05-03 21:42:17 -05:00
mdipierro e7f2e494af fixed Issue 1165:auth.signature: unable to reset is_active to true, thanks Ricardo 2013-05-03 21:37:26 -05:00
Michele Comitini ded9e5886d fix left over docstring after copying. 2013-05-03 17:35:03 +02:00
Michele Comitini d2757294af added support for custom state messages in HTTP 2013-05-03 17:30:25 +02:00
Michele Comitini 39da6a68fe Merge ssh://github.com/web2py/web2py 2013-05-03 17:23:17 +02:00
mdipierro 8e4bc72fca IS_IPADDRESS added to __all__ 2013-05-03 08:27:35 -05:00
mdipierro bc481e954c something went wrong with VCS, trying fix it 2013-05-02 20:32:21 -05:00
Michele Comitini e4ba924dc1 changed redirections to status code 302 to avoid issues with forms 2013-05-02 02:43:20 +02:00
mdipierro 193510b57d Merge pull request #86 from niphlod/feature/cache.action
cache.action now works with actions raising HTTP internally
2013-05-01 06:46:02 -07:00
mdipierro 88ad882d83 Merge pull request #85 from ilvalle/master
File list sidebar on Admin
2013-05-01 06:45:18 -07:00
mdipierro a90c4f7953 Merge pull request #84 from niphlod/issue/1464-1465
better T in grid/smartgrid
2013-05-01 06:44:04 -07:00
niphlod 0c2d97b9db cache.action now works with actions raising HTTP internally 2013-04-29 21:07:04 +02:00
ilvalle 1c77fdd9e1 'File list' sidebar when editing a page in admin 2013-04-29 08:52:24 +02:00
mdipierro 681bc9755e fixed potential vulnerability in form CRSF handling, thanks Anthony 2013-04-28 23:09:49 -05:00
mdipierro 31e992696c fixed potential vulnerability in form CRSF handling, thanks Anthony 2013-04-28 23:05:40 -05:00
mdipierro 813411a408 grid.rows 2013-04-28 17:32:10 -05:00
niphlod 1702d1ac1f removed unused imports, fixes issue 1464 (wrong and missing T in grid and smartgrid), fixes issue 1465 (translation of "None" message) 2013-04-28 20:31:10 +02:00
mdipierro 022a2af8b1 Merge pull request #83 from yasinmuaz/patch-1
Turkish language support for Welcome app
2013-04-27 16:30:00 -07:00
yasinmuaz d46d16bc2b Update and rename tr-tr.py to tr.py 2013-04-27 23:35:44 +03:00
yasinmuaz ae3ff2313d Update tr-tr.py 2013-04-27 23:04:17 +03:00
yasinmuaz 669586f15e Turkish language support 2013-04-27 22:50:50 +03:00
mdipierro 46fbcc8d22 Merge pull request #82 from leonelcamara/patch-1
Mail - Added hostname to settings before locking
2013-04-26 08:32:00 -07:00
Massimo 178f94af8c fixed optional formatting of empty list:string 2013-04-25 18:10:56 -05:00
Leonel Câmara 2f6b3a0bda Mail - Added hostname to settings before locking
Added settings.hostname before locking settings, the class may need a comment explaining when is settings.hostname needed
2013-04-25 16:34:34 +02:00
mdipierro 97b70eff9f better populate 2013-04-24 18:05:22 -05:00
mdipierro 3efa77b57a Merge pull request #81 from niphlod/issue/1459
fixed issue 1459
2013-04-23 21:25:56 -07:00
mdipierro 48977ba04c Merge pull request #80 from niphlod/issue/1458
fixed issue 1458
2013-04-23 21:25:17 -07:00
mdipierro 94e3271481 fixed a long stanging problem with helo and hostname, thanks Leonel Câmara 2013-04-23 23:21:17 -05:00
niphlod 04f6e7ee38 fixed issue 1459 2013-04-23 23:04:12 +02:00
niphlod 15c9e685c8 fixed issue 1458 2013-04-23 22:35:43 +02:00
mdipierro b00463bdf4 fixed gluon/contrib/imageutils.py image path, thanks Tito Garrido 2013-04-21 19:50:55 -05:00
mdipierro c17e642f1f Merge pull request #79 from z4y4ts/master
Adds Query.__rand__ and dal.Query.__ror__
2013-04-20 10:39:55 -07:00
mdipierro a2e118ec50 fixed Issue 1456:encoding in mail.send, thanks Corne 2013-04-19 23:46:38 -05:00
Alexander Zayats 45bda63ed3 Adds Query.__rand__ and dal.Query.__ror__
Adds __rand__ == __and__ and __ror__ == __or__.

Also strips trailing spaces.
2013-04-18 16:15:06 +03:00
mdipierro c909af4a86 Merge pull request #77 from niphlod/feature/cache_count_grid
new arg to SQLFORM.grid to help with large datasets
2013-04-17 15:11:38 -07:00
mdipierro b5a7c7e30f Merge pull request #78 from niphlod/feature/test_markmin
add doctest hook for markmin. It wasn't run previously.
2013-04-17 15:11:29 -07:00
niphlod 6594e7a607 add doctest hook for markmin. It wasn't run previously. 2013-04-17 23:55:11 +02:00
niphlod 4d8dedeb27 new arg to SQLFORM.grid to help with large datasets 2013-04-17 23:01:01 +02:00
mdipierro b0e3e386e7 Merge pull request #76 from niphlod/feature/coverage
coverage integration
2013-04-16 14:19:58 -07:00
niphlod ad4870e338 windows-friendly coverage.ini, better handling for custom coverage settings, typo on contenttype.py 2013-04-16 22:47:12 +02:00
mdipierro c1f0bc3a6d fixed Issue 1453: Missing equivalent for Row.__int__ that works with new long-type record id, thanks Dominic 2013-04-15 22:58:05 -05:00
mdipierro 887d4cc136 enabled doctests (python 2.7 only) 2013-04-15 18:05:59 -05:00
mdipierro bbe787b7b4 new web2py_bootstrap.js, thanks Paolo Caruccio 2013-04-15 17:58:45 -05:00
mdipierro 7341988b01 hooking old doctests into unittests, thanks Niphlod 2013-04-15 17:19:11 -05:00
mdipierro 44eb880108 added test coverage link 2013-04-15 15:59:40 -05:00
niphlod c01f860009 added .coveragerc 2013-04-15 21:54:30 +02:00
niphlod f9315a8cc7 coverage integration, default .coveragerc and .travis.yml ready for coveralls.io 2013-04-15 21:50:36 +02:00
mdipierro 40918f44fd auto parsing of json content-type, thanks Niphlod 2013-04-15 10:16:18 -05:00
mdipierro 9fdb586d03 auth.requires_login() and ajax - don't force just 401, thanks Anthony 2013-04-15 10:06:25 -05:00
mdipierro 448c398341 Merge pull request #75 from michele-comitini/master
isempty() IS_NOT_IN_DB() speedup
2013-04-14 20:46:44 -07:00
mdipierro 618c322603 more more span6 in form in wiki 2013-04-12 23:33:36 -05:00
mdipierro b5c9ed633b fixed title-less layout 2013-04-12 23:30:19 -05:00
mdipierro 80342a22f5 fixed a bug in settings in wiki 2013-04-12 23:21:37 -05:00
Michele Comitini c999b16a27 Merge with upstream. 2013-04-13 00:04:26 +02:00
Michele Comitini 9bda793d46 orderby disabled where not needed 2013-04-13 00:03:43 +02:00
Massimo 5e9fbdd24f faster labels, thanks Anthony 2013-04-12 13:07:37 -05:00
Massimo 446e641e64 fixed issue 1443, automenu for wiki, thanks Alan 2013-04-12 10:46:58 -05:00
Massimo 7d53ec6d71 fixed loading or routes on winservice, thanks Stephen Tanner 2013-04-12 10:31:55 -05:00
mdipierro 7b0cf5bc12 fixed auth.settings.wiki and make populate generator 2013-04-10 22:14:59 -05:00
mdipierro a82034d516 auth.wiki(controller=...,function=...) always displays menu 2013-04-10 12:37:38 -05:00
mdipierro 4973361136 Merge branch 'master' of github.com:web2py/web2py 2013-04-10 11:09:44 -05:00
mdipierro 3b69d4bf7b Merge pull request #73 from wangganggithub/master
Add language file: zh-cn.py, zh-tw.py
2013-04-10 09:09:28 -07:00
wangganggithub fd5e14ea0d Create zh-tw.py
purely a clone of 'zh.py'.
rename to 'zh-tw.py' for more precise
2013-04-10 12:44:21 +08:00
wangganggithub 9fc3b2bc26 Update zh.py
fix 'langcode' from 'zh-cn' to 'zh-tw'.
2013-04-10 12:06:02 +08:00
wangganggithub 19cc977917 Create zh-cn.py
This is a specific language file for 'zh-cn'.
The former 'zh.py' should be renamed to 'zh-tw.py'.

web2py is a good project, thank you very much.
I wish I could do something for you all.
2013-04-10 12:04:49 +08:00
Massimo ce481d9002 fixed Issue 1439:ajax_error_500 var in web2py_ajax.html includes post vars, thanks Anthony 2013-04-09 16:46:48 -05:00
Massimo 2421a31b25 no links to media if page not saved 2013-04-09 16:42:41 -05:00
Massimo 94c79820b9 links with images in markmin 2013-04-09 16:33:45 -05:00
Massimo 80b38a743f [[http://link http://image img]] in markmin 2013-04-09 16:13:55 -05:00
Massimo af01b45852 ogg contenttype, thanks Ricardo 2013-04-09 16:00:25 -05:00
Massimo a2de6f12d1 ogg -> audio/ogg in contenttype 2013-04-09 09:20:28 -05:00
Massimo 2af81d2e7a fixed indonesian languages thanks Steve 2013-04-09 09:07:46 -05:00
mdipierro 0f691f2757 MARKMIN('<unicode>') 2013-04-08 21:45:19 -05:00
mdipierro be021db3d3 int -> long 2013-04-08 21:42:15 -05:00
mdipierro acd0ebd09d some clenup 2013-04-08 17:29:21 -05:00
mdipierro 3d5e594070 removed one more 2013-04-08 12:44:52 -05:00
mdipierro 58247e3d6b fixed Issue 1436:SQLFORM.grid search widget omits decimal data type 2013-04-08 12:40:52 -05:00
mdipierro 9f35635233 added Malesian and Indonesian languages, thanks Steve Van Christie 2013-04-08 11:13:15 -05:00
mdipierro 2119bacd22 Merge pull request #71 from grutz/IS_IPADDRESS
Adds IS_IPADDRESS() to gluon/validators.py
2013-04-07 19:41:25 -07:00
mdipierro d55fab540c Merge pull request #72 from grutz/auth_wiki_migrates
Add migrate control to Auth.wiki()
2013-04-07 19:39:26 -07:00
mdipierro 1344c50f85 fixed Issue 1434:admin page error listing with maximum recursion depth exceeded 2013-04-07 19:55:52 -05:00
mdipierro e8d97f5706 fixed issue 1433 & 885, login radius integration, thanks Nathan Freeze 2013-04-07 19:44:56 -05:00
mdipierro 35704f3527 new .travis.ci for Pypy testing, thanks Niphlod 2013-04-07 19:42:41 -05:00
mdipierro 96fb5b0755 pypi dal patch, thanks Niphlod 2013-04-07 19:37:32 -05:00
mdipierro 81f017a7c0 fixed OGV content-type 2013-04-06 12:22:46 -05:00
Kurt Grutzmacher b863ff048e Add migrate control to Auth.wiki()
The Auth.wiki() function does not allow for control of the migration
settings, always defaulting to migrate=True. In some instances the
developer may want to not force migration. This change adds the
ability to set the migrate option. Fake_migrate was not added but
can be if desired.
2013-04-05 09:25:20 -07:00
Kurt Grutzmacher 79a4693f34 Adds IS_IPADDRESS() to gluon/validators.py
IS_IPADDRESS() is mostly a meta function that will call IS_IPV4()
or IS_IPV6 accordingly based upon both dev-configured logic and
library checking of the address given. For instance:

 >>> IS_IPADDRESS()('192.168.1.5')
 ('192.168.1.5', None)

will run through the IS_IPV4() function since the ipaddr.py lib
recognizes it as an IPv4 address. Specific v4 or v6 validation
can be done by setting is_ipv4=True or is_ipv6=True:

 >>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af')
 ('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
 >>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1')
 ('192.168.1.1', 'enter valid IP address')

The same arguments for IS_IPV4() and IS_IPV6() are supported and
no changes have been made to either of these two functions. The
goal of this function is to allow IPv4 or IPv6 addresses in a
textfield:

 INPUT(_type='text', _name='name', requires=IS_IPADDRESS())
2013-04-05 09:14:37 -07:00
71 changed files with 3466 additions and 785 deletions
+19 -6
View File
@@ -4,22 +4,35 @@ python:
- '2.5'
- '2.6'
- '2.7'
- 'pypy'
install:
- pip install -e . --use-mirrors
env:
- DB=sqlite:memory
- DB=mysql://root:@localhost/test_w2p
- DB=postgres://postgres:@localhost/test_w2p
before_script:
- pip install unittest2
- if [[ $DB == postgres* ]]; then pip install --use-mirrors psycopg2; fi
- if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then pip install --use-mirrors unittest2; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors coverage; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install --use-mirrors python-coveralls; fi
- if [[ $DB == postgres* ]]; then pip install --use-mirrors psycopg2; fi;
- if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install --use-mirrors pysqlite; fi
- if [[ $DB == mysql* ]]; then mysql -e 'create database test_w2p;'; fi
- if [[ $DB == postgres* ]]; then psql -c 'create database test_w2p;' -U postgres; fi
#Temporal solution to travis issue #155
- sudo chmod 777 /dev/shm
- sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm
script: PYTHONPATH=. unit2 -v gluon.tests
matrix:
exclude:
- python: 'pypy'
env: DB=postgres://postgres:@localhost/test_w2p
- python: 'pypy'
env: DB=mysql://root:@localhost/test_w2p
script: export COVERAGE_PROCESS_START=gluon/tests/coverage.ini; ./web2py.py --run_system_tests --with_coverage
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coverage combine; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --config_file=gluon/tests/coverage.ini; fi
notifications:
email: false
irc:
channels: "irc.freenode.org#web2py"
email: true
+6
View File
@@ -1,3 +1,9 @@
## 2.4.7
- pypy support, thanks Niphlod
- more bug fixes
- ...
## 2.4.6
- better tests
+2 -1
View File
@@ -30,7 +30,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.4.6-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.4.7-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
@@ -118,6 +118,7 @@ commit:
push:
hg push
git push
git push --tags
tag:
git tag -l '$(S)'
hg tag -l '$(S)'
+2
View File
@@ -11,6 +11,8 @@ Learn more at http://web2py.com
[![Build Status](https://travis-ci.org/web2py/web2py.png)](https://travis-ci.org/web2py/web2py)
[![Coverage Status](https://coveralls.io/repos/web2py/web2py/badge.png)](https://coveralls.io/r/web2py/web2py)
## Installation Instructions
To start web2py there is NO NEED to install it. Just unzip and do:
+1 -1
View File
@@ -1 +1 @@
Version 2.4.6-stable+timestamp.2013.04.06.12.11.51
Version 2.4.7-stable+timestamp.2013.05.25.10.38.02
+3 -2
View File
@@ -279,14 +279,15 @@ def update():
(db, table) = get_table(request)
keyed = hasattr(db[table], '_primarykey')
record = None
db[table]._common_filter = None
if keyed:
key = [f for f in request.vars if f in db[table]._primarykey]
if key:
record = db(db[table][key[0]] == request.vars[key[
0]], ignore_common_filters=True).select().first()
0]]).select().first()
else:
record = db(db[table].id == request.args(
2), ignore_common_filters=True).select().first()
2)).select().first()
if not record:
qry = query_by_table_type(table, db)
+4 -4
View File
@@ -138,17 +138,17 @@ def check_version():
session.forget()
session._unlock(response)
new_version, version_number = check_new_version(request.env.web2py_version,
WEB2PY_VERSION_URL)
new_version, version = check_new_version(request.env.web2py_version,
WEB2PY_VERSION_URL)
if new_version == -1:
return A(T('Unable to check for upgrades'), _href=WEB2PY_URL)
elif new_version != True:
return A(T('web2py is up to date'), _href=WEB2PY_URL)
elif platform.system().lower() in ('windows', 'win32', 'win64') and os.path.exists("web2py.exe"):
return SPAN('You should upgrade to version %s' % version_number)
return SPAN('You should upgrade to %s' % version.split('(')[0])
else:
return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version_number.split('-')[0])
return sp_button(URL('upgrade_web2py'), T('upgrade now to %s') % version.split('(')[0])
def logout():
+160 -129
View File
@@ -1,119 +1,126 @@
# coding: utf8
# Translation by: Klaus Kappel <kkappel@yahoo.de>
{
'!langcode!': 'de',
'!langname!': 'Deutsch',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse können nicht aktualisiert oder gelöscht werden',
'%s %%{row} deleted': '%s Zeilen gelöscht',
'%s %%{row} updated': '%s Zeilen aktualisiert',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(requires internet access)': '(requires internet access)',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"Update" ist ein optionaler Ausdruck wie "Feld1 = \'newvalue". JOIN Ergebnisse k?nnen nicht aktualisiert oder gel?scht werden',
'%s %%{row} deleted': '%s %%{row} Zeilen gel?scht',
'%s %%{row} updated': '%s %%{row} Zeilen aktualisiert',
'%Y-%m-%d': '%d.%m.%Y',
'%Y-%m-%d %H:%M:%S': '%d.%m.%Y %H:%M:%S',
'(requires internet access)': '(Internet Zugang wir ben?tigt)',
'(requires internet access, experimental)': '(ben?tigt Internet Zugang)',
'(something like "it-it")': '(so etwas wie "it-it")',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'A new version of web2py is available': 'Eine neue Version von web2py ist verfügbar',
'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verfügbar: %s',
'@markmin\x01Searching: **%s** %%{file}': '@markmin\x01Suche: **%s** Dateien',
'A new version of web2py is available': 'Eine neue Version von web2py ist verf?gbar',
'A new version of web2py is available: %s': 'Eine neue Version von web2py ist verf?gbar: %s',
'Abort': 'Abbrechen',
'About': 'Über',
'About application': 'Über die Anwendung',
'Additional code for your application': 'Additional code for your application',
'additional code for your application': 'zusätzlicher Code für Ihre Anwendung',
'admin disabled because no admin password': ' admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist',
'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert dafür keine Unterstützung auf der google apps engine',
'About': '?ber',
'About application': '?ber die Anwendung',
'Additional code for your application': 'zus?tzlicher Code f?r Ihre Anwendung',
'admin disabled because no admin password': 'admin ist deaktiviert, weil kein Admin-Passwort gesetzt ist',
'admin disabled because not supported on google apps engine': 'admin ist deaktiviert, es existiert daf?r keine Unterst?tzung auf der google apps engine',
'admin disabled because unable to access password file': 'admin ist deaktiviert, weil kein Zugriff auf die Passwortdatei besteht',
'Admin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
'Admin is disabled because unsecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
'Admin language': 'Admin language',
'administrative interface': 'administrative interface',
'Admin language': 'Admin-Sprache',
'administrative interface': 'Administrative Schnittstelle',
'Administrator Password:': 'Administrator Passwort:',
'An error occured, please %s the page': 'Ein Fehler ist aufgetereten, bitte %s die Seite',
'and rename it (required):': 'und benenne sie um (erforderlich):',
'and rename it:': ' und benenne sie um:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'Appadmin ist deaktiviert, wegen der Benutzung eines unsicheren Kanals',
'Application': 'Anwendung',
'application "%s" uninstalled': 'Anwendung "%s" deinstalliert',
'application compiled': 'Anwendung kompiliert',
'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr geändert werden',
'Application name:': 'Application name:',
'Are you sure you want to delete file "%s"?': 'Sind Sie sich sicher, dass Sie diese Datei löschen wollen "%s"?',
'application is compiled and cannot be designed': 'Die Anwendung ist kompiliert kann deswegen nicht mehr ge?ndert werden',
'Application name:': 'Name der Applikation:',
'Are you sure you want to delete file "%s"?': 'Sind Sie sich sicher, dass Sie diese Datei l?schen wollen "%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"': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"',
'Are you sure you want to uninstall application "%s"?': 'Sind Sie sich sicher, dass Sie diese Anwendung deinstallieren wollen "%s"?',
'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden möchten?',
'Are you sure you want to upgrade web2py now?': 'Sind Sie sich sicher, dass Sie web2py jetzt upgraden m?chten?',
'arguments': 'arguments',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl benötigt eine sichere (HTTPS) Verbindung. Es sei denn sie läuft Lokal(localhost).',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. Führen sie also nicht mehrere Tests gleichzeitig aus.',
'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und benötigt noch weitere Tests.',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ACHTUNG: Die Einwahl ben?tigt eine sichere (HTTPS) Verbindung. Es sei denn sie l?uft Lokal(localhost).',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ACHTUNG: Testen ist nicht threadsicher. F?hren sie also nicht mehrere Tests gleichzeitig aus.',
'ATTENTION: This is an experimental feature and it needs more testing.': 'ACHTUNG: Dies ist eine experimentelle Funktion und ben?tigt noch weitere Tests.',
'ATTENTION: you cannot edit the running application!': 'ACHTUNG: Eine laufende Anwendung kann nicht editiert werden!',
'Authentication': 'Authentifizierung',
'Available databases and tables': 'Verfügbare Datenbanken und Tabellen',
'back': 'zurück',
'beautify': 'beautify',
'cache': 'Cache',
'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gelöscht',
'call': 'call',
'can be a git repo': 'can be a git repo',
'Available databases and tables': 'Verf?gbare Datenbanken und Tabellen',
'back': 'zur?ck',
'beautify': 'versch?nern',
'cache': 'Pufferspeicher',
'cache, errors and sessions cleaned': 'Zwischenspeicher (cache), Fehler und Sitzungen (sessions) gel?scht',
'call': 'Aufruf',
'can be a git repo': 'kann ein git Repository sein',
'Cannot be empty': 'Darf nicht leer sein',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Nicht Kompilierbar:Es sind Fehler in der Anwendung. Beseitigen Sie die Fehler und versuchen Sie es erneut.',
'cannot create file': 'Kann Datei nicht erstellen',
'cannot upload file "%(filename)s"': 'Kann Datei nicht Hochladen "%(filename)s"',
'Change admin password': 'Administrator-Passwort ändern',
'Change Password': 'Passwort ändern',
'change password': 'Passwort ändern',
'check all': 'alles auswählen',
'Change admin password': 'Administrator-Passwort ?ndern',
'Change Password': 'Passwort ?ndern',
'change password': 'Passwort ?ndern',
'check all': 'alles ausw?hlen',
'Check for upgrades': 'check for upgrades',
'Check to delete': 'Markiere zum löschen',
'Checking for upgrades...': 'Auf Updates überprüfen...',
'Clean': 'löschen',
'click here for online examples': 'hier klicken für online Beispiele',
'click here for the administrative interface': 'hier klicken für die Administrationsoberfläche ',
'Check to delete': 'Markiere zum l?schen',
'Checking for upgrades...': 'Auf Updates ?berpr?fen...',
'Clean': 'leeren',
'click here for online examples': 'hier klicken f?r online Beispiele',
'click here for the administrative interface': 'hier klicken f?r die Administrationsoberfl?che ',
'Click row to expand traceback': 'Klicke auf die Zeile f?r Fehlerverfolgung',
'click to check for upgrades': 'hier klicken um nach Upgrades zu suchen',
'Client IP': 'Client IP',
'code': 'code',
'collapse/expand all': 'collapse/expand all',
'collapse/expand all': 'alles zu- bzw. aufklappen',
'Compile': 'kompilieren',
'compiled application removed': 'kompilierte Anwendung gelöscht',
'compiled application removed': 'kompilierte Anwendung gel?scht',
'Controller': 'Controller',
'Controllers': 'Controller',
'controllers': 'Controllers',
'Copyright': 'Urheberrecht',
'Count': 'Anzahl',
'Create': 'erstellen',
'create file with filename:': 'erzeuge Datei mit Dateinamen:',
'create new application:': 'erzeuge neue Anwendung:',
'Create new simple application': 'Erzeuge neue Anwendung',
'created by': 'created by',
'created by': 'erstellt von',
'crontab': 'crontab',
'Current request': 'Aktuelle Anfrage (request)',
'Current response': 'Aktuelle Antwort (response)',
'Current session': 'Aktuelle Sitzung (session)',
'currently running': 'currently running',
'currently running': 'aktuell in Betrieb',
'currently saved or': 'des derzeit gespeicherten oder',
'customize me!': 'pass mich an!',
'data uploaded': 'Daten hochgeladen',
'Database': 'Datenbank',
'database': 'Datenbank',
'database %s select': 'Datenbank %s ausgewählt',
'database %s select': 'Datenbank %s ausgew?hlt',
'database administration': 'Datenbankadministration',
'Date and Time': 'Datum und Uhrzeit',
'db': 'db',
'DB Model': 'DB Modell',
'Debug': 'Debug',
'defines tables': 'definiere Tabellen',
'Delete': 'Löschen',
'delete': 'löschen',
'delete all checked': 'lösche alle markierten',
'delete plugin': 'Plugin löschen',
'Delete:': 'Löschen:',
'Deploy': 'deploy',
'Delete': 'L?schen',
'delete': 'l?schen',
'delete all checked': 'l?sche alle markierten',
'delete plugin': 'Plugin l?schen',
'Delete:': 'L?schen:',
'Deploy': 'Installieren',
'Deploy on Google App Engine': 'Auf Google App Engine installieren',
'Deploy to OpenShift': 'Deploy to OpenShift',
'Deploy to OpenShift': 'Auf OpenShift installieren',
'Description': 'Beschreibung',
'design': 'design',
'DESIGN': 'design',
'Design for': 'Design für',
'Design for': 'Design f?r',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direction: ltr',
'Disable': 'Disable',
'Disable': 'Deaktivieren',
'documentation': 'Dokumentation',
'done!': 'fertig!',
'download layouts': 'download layouts',
'Download .w2p': 'Download .w2p',
'download layouts': 'Layouts herunterladen',
'download plugins': 'download plugins',
'E-mail': 'E-mail',
'EDIT': 'BEARBEITEN',
@@ -129,56 +136,64 @@
'Editing file "%s"': 'Bearbeite Datei "%s"',
'Editing Language file': 'Sprachdatei bearbeiten',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error logs for "%(app)s"': 'Fehlerprotokoll für "%(app)s"',
'Error': 'Fehler',
'Error logs for "%(app)s"': 'Fehlerprotokoll f?r "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'Fehler',
'escape': 'escape',
'Exception instance attributes': 'Atribute der Ausnahmeinstanz',
'Expand Abbreviation': 'Kürzel erweitern',
'Expand Abbreviation': 'K?rzel erweitern',
'export as csv file': 'Exportieren als CSV-Datei',
'exposes': 'stellt zur Verfügung',
'exposes': 'stellt zur Verf?gung',
'extends': 'erweitert',
'failed to reload module': 'neu laden des Moduls fehlgeschlagen',
'File': 'Datei',
'file "%(filename)s" created': 'Datei "%(filename)s" erstellt',
'file "%(filename)s" deleted': 'Datei "%(filename)s" gelöscht',
'file "%(filename)s" deleted': 'Datei "%(filename)s" gel?scht',
'file "%(filename)s" uploaded': 'Datei "%(filename)s" hochgeladen',
'file "%(filename)s" was not deleted': 'Datei "%(filename)s" wurde nicht gelöscht',
'file "%(filename)s" was not deleted': 'Datei "%(filename)s" wurde nicht gel?scht',
'file "%s" of %s restored': 'Datei "%s" von %s wiederhergestellt',
'file changed on disk': 'Datei auf Festplatte geändert',
'file changed on disk': 'Datei auf Festplatte ge?ndert',
'file does not exist': 'Datei existiert nicht',
'file saved on %(time)s': 'Datei gespeichert am %(time)s',
'file saved on %s': 'Datei gespeichert auf %s',
'filter': 'filter',
'First name': 'Vorname',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Funktionen ohne doctests erzeugen [passed] in Tests',
'Get from URL:': 'Get from URL:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Go to Matching Pair': 'gehe zum übereinstimmenden Paar',
'Go to Matching Pair': 'gehe zum ?bereinstimmenden Paar',
'Goto': 'Goto',
'Group ID': 'Gruppen ID',
'Hello World': 'Hallo Welt',
'Help': 'Hilfe',
'Home': 'Home',
'htmledit': 'htmledit',
'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.': 'Falls der obere Test eine Fehler-Ticketnummer enthält deutet das auf einen Fehler in der Ausführung des Controllers hin, noch bevor der Doctest ausgeführt werden konnte. Gewöhnlich führen fehlerhafte Einrückungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein grüner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.',
'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.': 'Falls der obere Test eine Fehler-Ticketnummer enth?lt deutet das auf einen Fehler in der Ausf?hrung des Controllers hin, noch bevor der Doctest ausgef?hrt werden konnte. Gew?hnlich f?hren fehlerhafte Einr?ckungen oder fehlerhafter Code ausserhalb der Funktion zu solchen Fehlern. Ein gr?ner Titel deutet darauf hin, dass alle Test(wenn sie vorhanden sind) erfolgreich durchlaufen wurden. In diesem Fall werden die Testresultate nicht angezeigt.',
'If you answer "yes", be patient, it may take a while to download': '',
'If you answer yes, be patient, it may take a while to download': 'If you answer yes, be patient, it may take a while to download',
'Import/Export': 'Importieren/Exportieren',
'includes': 'Einfügen',
'includes': 'Einf?gen',
'Index': 'Index',
'index': 'index',
'insert new': 'neu einfügen',
'insert new %s': 'neu einfügen %s',
'insert new': 'neu einf?gen',
'insert new %s': 'neu einf?gen %s',
'inspect attributes': 'inspect attributes',
'Install': 'installieren',
'Installed applications': 'Installierte Anwendungen',
'internal error': 'interner Fehler',
'Internal State': 'interner Status',
'Invalid action': 'Ungültige Aktion',
'Invalid email': 'Ungültige Email',
'invalid password': 'Ungültiges Passwort',
'Invalid Query': 'Ungültige Abfrage',
'invalid request': 'ungültige Anfrage',
'invalid ticket': 'ungültiges Ticket',
'Invalid action': 'Ung?ltige Aktion',
'Invalid email': 'Ung?ltige Email',
'invalid password': 'Ung?ltiges Passwort',
'Invalid Query': 'Ung?ltige Abfrage',
'invalid request': 'ung?ltige Anfrage',
'invalid ticket': 'ung?ltiges Ticket',
'Key bindings': 'Tastenbelegungen',
'Key bindings for ZenConding Plugin': 'Tastenbelegungen für das ZenConding Plugin',
'Key bindings for ZenConding Plugin': 'Tastenbelegungen f?r das ZenConding Plugin',
'language file "%(filename)s" created/updated': 'Sprachdatei "%(filename)s" erstellt/aktualisiert',
'Language files (static strings) updated': 'Sprachdatei (statisch Strings) aktualisiert',
'languages': 'Sprachen',
@@ -187,8 +202,9 @@
'Last name': 'Nachname',
'Last saved on:': 'Zuletzt gespeichert am:',
'Layout': 'Layout',
'License for': 'Lizenz für',
'License for': 'Lizenz f?r',
'loading...': 'lade...',
'locals': 'locals',
'located in the file': 'located in Datei',
'Login': 'Anmelden',
'login': 'anmelden',
@@ -196,33 +212,37 @@
'Logout': 'abmelden',
'Lost Password': 'Passwort vergessen',
'lost password?': 'Passwort vergessen?',
'Main Menu': 'Menú principal',
'Main Menu': 'Men? principal',
'Manage': 'Verwalten',
'Match Pair': 'Paare finden',
'Menu Model': 'Menü Modell',
'Menu Model': 'Men? Modell',
'merge': 'verbinden',
'Merge Lines': 'Zeilen zusammenfügen',
'Merge Lines': 'Zeilen zusammenf?gen',
'Models': 'Modelle',
'models': 'Modelle',
'Modules': 'Module',
'modules': 'Module',
'Name': 'Name',
'new application "%s" created': 'neue Anwendung "%s" erzeugt',
'New application wizard': 'New application wizard',
'New application wizard': 'Neue Anwendung per Assistent',
'New Record': 'Neuer Datensatz',
'new record inserted': 'neuer Datensatz eingefügt',
'New simple application': 'New simple application',
'next 100 rows': 'nächsten 100 Zeilen',
'Next Edit Point': 'nächster Bearbeitungsschritt',
'new record inserted': 'neuer Datensatz eingef?gt',
'New simple application': 'Neue einfache Anwendung',
'next 100 rows': 'n?chsten 100 Zeilen',
'Next Edit Point': 'n?chster Bearbeitungsschritt',
'NO': 'NEIN',
'No databases in this application': 'Keine Datenbank in dieser Anwendung',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'Or Get from URL:': 'oder hole es von folgender URL:',
'or import from csv file': 'oder importieren von cvs Datei',
'or provide app url:': 'oder geben Sie eine Anwendungs-URL an:',
'or provide application url:': 'oder geben Sie eine Anwendungs-URL an:',
'Origin': 'Herkunft',
'Original/Translation': 'Original/Übersetzung',
'Overwrite installed app': 'installierte Anwendungen überschreiben',
'Original/Translation': 'Original/?bersetzung',
'Overwrite installed app': 'installierte Anwendungen ?berschreiben',
'Pack all': 'verpacke alles',
'Pack compiled': 'Verpacke kompiliert',
'Pack custom': 'Verpacke individuell',
'pack plugin': 'Plugin verpacken',
'Password': 'Passwort',
'Peeking at file': 'Dateiansicht',
@@ -230,9 +250,10 @@
'Plugin "%s" in application': 'Plugin "%s" in Anwendung',
'plugins': 'plugins',
'Plugins': 'Plugins',
'Powered by': 'Unterstützt von',
'Powered by': 'Unterst?tzt von',
'previous 100 rows': 'vorherige 100 zeilen',
'Previous Edit Point': 'vorheriger Bearbeitungsschritt',
'Project Progress': 'Projekt Fortschritt',
'Query:': 'Abfrage:',
'record': 'Datensatz',
'record does not exist': 'Datensatz existiert nicht',
@@ -240,33 +261,38 @@
'Record ID': 'Datensatz ID',
'register': 'Registrierung',
'Register': 'registrieren',
'Registration key': 'Registrierungsschlüssel',
'Reload routes': 'Reload routes',
'Remove compiled': 'kompilat gelöscht',
'Reset Password key': 'Passwortschlüssel zurücksetzen',
'Registration key': 'Registrierungsschl?ssel',
'reload': 'Neu laden',
'Reload routes': 'Routen neu laden',
'Remove compiled': 'Bytecode l?schen',
'request': 'request',
'Reset Password key': 'Passwortschl?ssel zur?cksetzen',
'Resolve Conflict file': 'bereinige Konflikt-Datei',
'response': 'Antwort',
'restore': 'wiederherstellen',
'revert': 'zurückkehren',
'revert': 'zur?ckkehren',
'Role': 'Rolle',
'Rows in table': 'Zeilen in Tabelle',
'Rows selected': 'Zeilen ausgewählt',
'Running on %s': 'Running on %s',
'Rows selected': 'Zeilen ausgew?hlt',
'Running on %s': 'L?uft auf %s',
'save': 'sichern',
'Save via Ajax': 'via Ajax sichern',
'Saved file hash:': 'Gespeicherter Datei-Hash:',
'selected': 'ausgewählt(e)',
'session expired': 'Sitzung Abgelaufen',
'Select Files to Package': 'Dateien zum Paketieren w?hlen',
'selected': 'ausgew?hlt(e)',
'session': 'Sitzung',
'session expired': 'Sitzung abgelaufen',
'shell': 'shell',
'Site': 'Seite',
'some files could not be removed': 'einige Dateien konnten nicht gelöscht werden',
'Start wizard': 'start wizard',
'some files could not be removed': 'einige Dateien konnten nicht gel?scht werden',
'Start wizard': 'Assistent starten',
'state': 'Status',
'static': 'statische Dateien',
'Static files': 'statische Dateien',
'Stylesheet': 'Stylesheet',
'Submit': 'Submit',
'submit': 'Absenden',
'Sure you want to delete this object?': 'Wollen Sie das Objekt wirklich löschen?',
'Sure you want to delete this object?': 'Wollen Sie das Objekt wirklich l?schen?',
'table': 'Tabelle',
'Table name': 'Tabellen Name',
'test': 'Test',
@@ -276,42 +302,45 @@
'test_try': 'test_try',
'Testing application': 'Teste die Anwendung',
'Testing controller': 'teste Controller',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Die "query" ist eine Bedingung wie "db.table1.field1 == \'Wert\'". Etwas wie "db.table1.field1 db.table2.field2 ==" führt zu einem SQL JOIN.',
'the application logic, each URL path is mapped in one exposed function in the controller': 'Die Logik der Anwendung, jeder URL-Pfad wird auf eine Funktion abgebildet die der Controller zur Verfügung stellt',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'Die "query" ist eine Bedingung wie "db.table1.field1 == \'Wert\'". Etwas wie "db.table1.field1 db.table2.field2 ==" f?hrt zu einem SQL JOIN.',
'the application logic, each URL path is mapped in one exposed function in the controller': 'Die Logik der Anwendung, jeder URL-Pfad wird auf eine Funktion abgebildet die der Controller zur Verf?gung stellt',
'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',
'the data representation, define database tables and sets': 'Die Datenrepräsentation definiert Mengen von Tabellen und Datenbanken ',
'the data representation, define database tables and sets': 'Die Datenrepr?sentation definiert Mengen von Tabellen und Datenbanken ',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The output of the file is a dictionary that was rendered by the view': 'The output of the file is a dictionary that was rendered by the view',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'the presentations layer, views are also known as templates': 'Die Präsentationsschicht, Views sind auch bekannt als Vorlagen/Templates',
'the presentations layer, views are also known as templates': 'Die Pr?sentationsschicht, Views sind auch bekannt als Vorlagen/Templates',
'There are no controllers': 'Keine Controller vorhanden',
'There are no models': 'Keine Modelle vorhanden',
'There are no modules': 'Keine Module vorhanden',
'There are no plugins': 'There are no plugins',
'There are no static files': 'Keine statischen Dateien vorhanden',
'There are no translators, only default language is supported': 'Keine Übersetzungen vorhanden, nur die voreingestellte Sprache wird unterstützt',
'There are no translators, only default language is supported': 'Keine ?bersetzungen vorhanden, nur die voreingestellte Sprache wird unterst?tzt',
'There are no views': 'Keine Views vorhanden',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'these files are served without processing, your images go here': 'Diese Dateien werden ohne Verarbeitung ausgeliefert. Beispielsweise Bilder kommen hier hin.',
'This is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundgerüst-Anwendung',
'This is a copy of the scaffolding application': 'Dies ist eine Kopie einer Grundger?st-Anwendung',
'This is the %(filename)s template': 'Dies ist das Template %(filename)s',
'Ticket': 'Ticket',
'Timestamp': 'Timestamp',
'Ticket ID': 'Ticket ID',
'Timestamp': 'Zeitstempel',
'TM': 'TM',
'to previous version.': 'zu einer früheren Version.',
'to previous version.': 'zu einer fr?heren Version.',
'To create a plugin, name a file/folder plugin_[name]': 'Um ein Plugin zu erstellen benennen Sie eine(n) Datei/Ordner plugin_[Name]',
'translation strings for the application': 'Übersetzungs-Strings für die Anwendung',
'Translation strings for the application': 'Translation strings for the application',
'Traceback': 'Traceback',
'translation strings for the application': '?bersetzungs-Strings f?r die Anwendung',
'Translation strings for the application': '?bersetzungs-Strings f?r die Anwendung',
'try': 'versuche',
'try something like': 'versuche so etwas wie',
'Unable to check for upgrades': 'überprüfen von Upgrades nicht möglich',
'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht möglich',
'unable to delete file "%(filename)s"': 'löschen von Datein "%(filename)s" nicht möglich',
'Unable to download': 'herunterladen nicht möglich',
'Unable to download app': 'herunterladen der Anwendung nicht möglich',
'unable to parse csv file': 'analysieren der cvs Datei nicht möglich',
'unable to uninstall "%s"': 'deinstallieren von "%s" nicht möglich',
'uncheck all': 'alles demarkieren',
'Try the mobile interface': 'Try the mobile interface',
'Unable to check for upgrades': '?berpr?fen von Upgrades nicht m?glich',
'unable to create application "%s"': 'erzeugen von Anwendung "%s" nicht m?glich',
'unable to delete file "%(filename)s"': 'l?schen von Datein "%(filename)s" nicht m?glich',
'Unable to download': 'herunterladen nicht m?glich',
'Unable to download app': 'herunterladen der Anwendung nicht m?glich',
'unable to parse csv file': 'analysieren der cvs Datei nicht m?glich',
'unable to uninstall "%s"': 'deinstallieren von "%s" nicht m?glich',
'uncheck all': 'Selektionen entfernen',
'Uninstall': 'deinstallieren',
'update': 'aktualisieren',
'update all languages': 'aktualisiere alle Sprachen',
@@ -319,33 +348,35 @@
'upgrade web2py now': 'jetzt web2py upgraden',
'upload': 'upload',
'Upload & install packed application': 'Verpackte Anwendung hochladen und installieren',
'Upload a package:': 'Upload a package:',
'Upload and install packed application': 'Upload and install packed application',
'Upload a package:': 'Ein Packet hochladen:',
'Upload and install packed application': 'Verpackte Anwendung hochladen und installieren',
'upload application:': 'lade Anwendung hoch:',
'Upload existing application': 'lade existierende Anwendung hoch',
'upload file:': 'lade Datei hoch:',
'upload plugin file:': 'Plugin-Datei hochladen:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) für AND, (...)|(...) für OR, und ~(...) für NOT, um komplexe Abfragen zu erstellen.',
'Use an url:': 'Use an url:',
'user': 'user',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Benutze (...)&(...) f?r AND, (...)|(...) f?r OR, und ~(...) f?r NOT, um komplexe Abfragen zu erstellen.',
'Use an url:': 'Verwende URL:',
'user': 'Nutzer',
'User ID': 'Benutzer ID',
'variables': 'variables',
'variables': 'Variablen',
'Version': 'Version',
'Version %s.%s.%s (%s) %s': 'Version %s.%s.%s (%s) %s',
'versioning': 'Versionierung',
'View': 'View',
'view': 'View',
'Views': 'Views',
'views': 'Views',
'Versioning': 'Versionierung',
'View': 'Ansicht',
'view': 'Ansicht',
'Views': 'Ansichten',
'views': 'Ansichten',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py ist auf dem neuesten Stand',
'web2py Recent Tweets': 'neuste Tweets von web2py',
'Welcome %s': 'Willkommen %s',
'Welcome to web2py': 'Willkommen zu web2py',
'Which called the function': 'Which called the function',
'Wrap with Abbreviation': 'mit Kürzel einhüllen',
'Which called the function': 'welche die Funktion aufrief',
'Wrap with Abbreviation': 'mit K?rzel einh?llen',
'xml': 'xml',
'YES': 'JA',
'You are successfully running web2py': 'web2by wird erfolgreich ausgeführt',
'You can modify this application and adapt it to your needs': 'Sie können diese Anwendung verändern und Ihren Bedürfnissen anpassen',
'You are successfully running web2py': 'web2by wird erfolgreich ausgef?hrt',
'You can modify this application and adapt it to your needs': 'Sie k?nnen diese Anwendung ver?ndern und Ihren Bed?rfnissen anpassen',
'You visited the url': 'Sie besuchten die URL',
}
+96 -96
View File
@@ -5,11 +5,11 @@
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression en option tels que "field1 = \'newvalue\'". Vous ne pouvez pas mettre à jour ou supprimer les résultats d\'une jointure "a JOIN"',
'%Y-%m-%d': '%d-%m-%Y',
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
'%s %%{row} deleted': 'lignes %s supprimé',
'%s %%{row} updated': 'lignes %s mis à jour',
'(requires internet access)': '(requires internet access)',
'%s %%{row} deleted': 'lignes %s supprimées',
'%s %%{row} updated': 'lignes %s mises à jour',
'(requires internet access)': '(nécessite un accès Internet)',
'(something like "it-it")': '(quelque chose comme "it-it") ',
'@markmin\x01Searching: **%s** %%{file}': 'Searching: **%s** files',
'@markmin\x01Searching: **%s** %%{file}': 'Cherche: **%s** fichiers',
'A new version of web2py is available: %s': 'Une nouvelle version de web2py est disponible: %s ',
'A new version of web2py is available: Version 1.68.2 (2009-10-21 09:59:29)\n': 'Une nouvelle version de web2py est disponible: Version 1.68.2 (2009-10-21 09:59:29)\r\n',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': 'ATTENTION: nécessite une connexion sécurisée (HTTPS) ou être en localhost. ',
@@ -17,59 +17,59 @@
'ATTENTION: you cannot edit the running application!': "ATTENTION: vous ne pouvez pas modifier l'application qui tourne!",
'About': 'à propos',
'About application': "A propos de l'application",
'Additional code for your application': 'Additional code for your application',
'Additional code for your application': 'Code additionnel pour votre application',
'Admin is disabled because insecure channel': 'Admin est désactivé parce que canal non sécurisé',
'Admin language': 'Admin language',
'Admin language': "Language de l'admin",
'Administrator Password:': 'Mot de passe Administrateur:',
'Application name:': 'Application name:',
'Are you sure you want to delete file "%s"?': 'Etes-vous sûr de vouloir supprimer le fichier «%s»?',
'Are you sure you want to delete plugin "%s"?': 'Etes-vous sûr de vouloir effacer le plugin "%s"?',
'Are you sure you want to delete this object?': 'Are you sure you want to delete this object?',
'Application name:': "Nom de l'application:",
'Are you sure you want to delete file "%s"?': 'Êtes-vous sûr de vouloir supprimer le fichier «%s»?',
'Are you sure you want to delete plugin "%s"?': 'Êtes-vous sûr de vouloir supprimer le plugin "%s"?',
'Are you sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Are you sure you want to uninstall application "%s"?': "Êtes-vous sûr de vouloir désinstaller l'application «%s»?",
'Are you sure you want to upgrade web2py now?': 'Are you sure you want to upgrade web2py now?',
'Are you sure you want to upgrade web2py now?': 'Êtes-vous sûr de vouloir mettre à jour web2py maintenant?',
'Available databases and tables': 'Bases de données et tables disponible',
'Cannot be empty': 'Ne peut pas être vide',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': 'Ne peut pas compiler: il y a des erreurs dans votre application. corriger les erreurs et essayez à nouveau.',
'Cannot compile: there are errors in your app:': 'Cannot compile: there are errors in your app:',
'Change admin password': 'change admin password',
'Check for upgrades': 'check for upgrades',
'Cannot compile: there are errors in your app:': 'Ne peut pas compiler: il y a des erreurs dans votre application:',
'Change admin password': 'Changer le mot de passe admin',
'Check for upgrades': 'Vérifier les mises à jour',
'Check to delete': 'Cocher pour supprimer',
'Checking for upgrades...': 'Vérification des mises à jour ... ',
'Clean': 'nettoyer',
'Compile': 'compiler',
'Controllers': 'Contrôleurs',
'Create': 'create',
'Create': 'Créer',
'Create new simple application': 'Créer une nouvelle application',
'Current request': 'Requête actuel',
'Current request': 'Requête actuelle',
'Current response': 'Réponse actuelle',
'Current session': 'Session en cours',
'Date and Time': 'Date et heure',
'Delete': 'Supprimer',
'Delete this file (you will be asked to confirm deletion)': 'Delete this file (you will be asked to confirm deletion)',
'Delete this file (you will be asked to confirm deletion)': 'Supprimer ce fichier (on vous demandera de confirmer la suppression)',
'Delete:': 'Supprimer:',
'Deploy': 'deploy',
'Deploy': 'Déployer',
'Deploy on Google App Engine': 'Déployer sur Google App Engine',
'EDIT': 'MODIFIER',
'Edit': 'modifier',
'Edit application': "Modifier l'application",
'Edit current record': 'Modifier cet entrée',
'Edit current record': 'Modifier cette entrée',
'Editing Language file': 'Modifier le fichier de langue',
'Editing file': 'Modifier le fichier',
'Editing file "%s"': 'Modifier le fichier "% s" ',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error logs for "%(app)s"': 'Journal d\'erreurs pour "%(app)s"',
'Errors': 'erreurs',
'Exception instance attributes': 'Exception instance attributes',
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraînera tests [passed] .',
'Exception instance attributes': "Attributs d'instance Exception",
'Functions with no doctests will result in [passed] tests.': 'Des fonctions sans doctests entraîneront des tests [passed] .',
'Help': 'aide',
'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.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre verte indique que tous les tests (si définie) passed. Dans ce cas, les résultats des essais ne sont pas affichées.",
'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.': "Si le rapport ci-dessus contient un numéro de ticket, cela indique une défaillance dans l'exécution du contrôleur, avant toute tentative d'exécuter les doctests. Cela est généralement dû à une erreur d'indentation ou une erreur à l'extérieur du code de la fonction.\r\nUn titre vert indique que tous les tests (si définis) sont passés. Dans ce cas, les résultats des essais ne sont pas affichées.",
'Import/Export': 'Importer/Exporter',
'Install': 'install',
'Installed applications': 'Les applications installées',
'Install': 'Installer',
'Installed applications': 'Applications installées',
'Internal State': 'État Interne',
'Invalid Query': 'Requête non valide',
'Invalid action': 'Action non valide',
'Language files (static strings) updated': 'Fichiers de langue (static strings) Mise à jour ',
'Language files (static strings) updated': 'Fichiers de langue (chaînes statiques) mis à jour ',
'Languages': 'Langues',
'Last saved on:': 'Dernière sauvegarde le:',
'License for': 'Licence pour',
@@ -79,13 +79,13 @@
'Models': 'Modèles',
'Modules': 'Modules',
'NO': 'NON',
'New Record': 'Nouvel Entrée',
'New application wizard': 'New application wizard',
'New simple application': 'New simple application',
'New Record': 'Nouvelle Entrée',
'New application wizard': 'Assistant nouvelle application',
'New simple application': 'Nouvelle application simple',
'No databases in this application': 'Aucune base de données dans cette application',
'Original/Translation': 'Original / Traduction',
'Overwrite installed app': 'overwrite installed app',
'PAM authenticated user, cannot change password here': 'PAM authenticated user, cannot change password here',
'Overwrite installed app': "Écraser l'application installée",
'PAM authenticated user, cannot change password here': 'Utilisateur authentifié par PAM, vous ne pouvez pas changer le mot de passe ici',
'Pack all': 'tout empaqueter',
'Pack compiled': 'paquet compilé',
'Peeking at file': 'Jeter un oeil au fichier',
@@ -97,101 +97,101 @@
'Resolve Conflict file': 'Résoudre les conflits de fichiers',
'Rows in table': 'Lignes de la table',
'Rows selected': 'Lignes sélectionnées',
"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')",
'Save': 'Save',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Lancer les tests dans ce fichier (pour lancer tous les fichiers, vous pouvez également utiliser le bouton nommé 'test')",
'Save': 'Enregistrer',
'Saved file hash:': 'Hash du Fichier enregistré:',
'Site': 'site',
'Start wizard': 'start wizard',
'Site': 'Site',
'Start wizard': "Démarrer l'assistant",
'Static files': 'Fichiers statiques',
'Sure you want to delete this object?': 'Vous êtes sûr de vouloir supprimer cet objet? ',
'TM': 'MD',
'Testing application': "Test de l'application",
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.field1==\'value\'". Quelque chose comme "db.table1.field1==db.table2.field2" aboutit à un JOIN SQL.',
'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',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'There are no controllers': "Il n'existe pas de contrôleurs",
'There are no models': "Il n'existe pas de modèles",
'There are no modules': "Il n'existe pas de modules",
'There are no plugins': 'There are no plugins',
'There are no static files': "Il n'existe pas de fichiers statiques",
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, est prise en charge uniquement la langue par défaut",
'There are no views': "Il n'existe pas de vues",
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'The application logic, each URL path is mapped in one exposed function in the controller': "La logique de l'application, chaque chemin d'URL est mappé avec une fonction exposée dans le contrôleur",
'The data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
'The presentations layer, views are also known as templates': "Les couches de présentation, les vues sont également appelées modples",
'There are no controllers': "Il n'y a pas de contrôleurs",
'There are no models': "Il n'y a pas de modèles",
'There are no modules': "Il n'y a pas de modules",
'There are no plugins': "Il n'y a pas de plugins",
'There are no static files': "Il n'y a pas de fichiers statiques",
'There are no translators, only default language is supported': "Il n'y a pas de traducteurs, seule la langue par défaut est prise en charge",
'There are no views': "Il n'y a pas de vues",
'These files are served without processing, your images go here': 'Ces fichiers sont renvoyés sans traitement, vos images viennent ici',
'This is the %(filename)s template': 'Ceci est le modèle %(filename)s ',
'Ticket': 'Ticket',
'To create a plugin, name a file/folder plugin_[name]': 'Pour créer un plugin, créer un fichier /dossier plugin_[nom]',
'Translation strings for the application': 'Translation strings for the application',
'Unable to check for upgrades': 'Impossible de vérifier les mises à niveau',
'Translation strings for the application': "Chaînes de traduction pour l'application",
'Unable to check for upgrades': 'Impossible de vérifier les mises à jour',
'Unable to download': 'Impossible de télécharger',
'Unable to download app': 'Impossible de télécharger app',
'Unable to download app because:': 'Unable to download app because:',
'Unable to download because': 'Unable to download because',
'Unable to download app': "Impossible de télécharger l'app",
'Unable to download app because:': "Impossible de télécharger l'app car:",
'Unable to download because': 'Impossible de télécharger car',
'Uninstall': 'désinstaller',
'Update:': 'Mise à jour:',
'Upload & install packed application': 'Upload & install packed application',
'Upload a package:': 'Upload a package:',
'Upload existing application': 'charger une application existante',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT et construire des requêtes plus complexes. ',
'Use an url:': 'Use an url:',
'Upload & install packed application': "Charger & installer l'application empaquetée",
'Upload a package:': 'Charger un paquet:',
'Upload existing application': 'Charger une application existante',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Utilisez (...)&(...) pour AND, (...)|(...) pour OR, et ~(...) pour NOT afin de construire des requêtes plus complexes. ',
'Use an url:': 'Utiliser une url:',
'Version': 'Version',
'Views': 'Vues',
'Web Framework': 'Web Framework',
'Web Framework': 'Framework Web',
'YES': 'OUI',
'additional code for your application': 'code supplémentaire pour votre application',
'admin disabled because no admin password': 'admin désactivé car aucun mot de passe admin',
'admin disabled because not supported on google app engine': 'admin désactivé car non pris en charge sur Google Apps engine',
'admin disabled because unable to access password file': "admin désactivé car incapable d'accéder au fichier mot de passe",
'administrative interface': 'administrative interface',
'admin disabled because no admin password': 'admin désactivée car aucun mot de passe admin',
'admin disabled because not supported on google app engine': 'admin désactivée car non prise en charge sur Google Apps engine',
'admin disabled because unable to access password file': "admin désactivée car incapable d'accéder au fichier mot de passe",
'administrative interface': "interface d'administration",
'and rename it (required):': 'et renommez-la (obligatoire):',
'and rename it:': 'et renommez-le:',
'appadmin': 'appadmin',
'appadmin is disabled because insecure channel': 'appadmin est désactivé parce que canal non sécurisé',
'application "%s" uninstalled': 'application "%s" désinstallé',
'application %(appname)s installed with md5sum: %(digest)s': 'application %(appname)s installed with md5sum: %(digest)s',
'application "%s" uninstalled': 'application "%s" désinstallée',
'application %(appname)s installed with md5sum: %(digest)s': 'application %(appname)s installée avec md5sum: %(digest)s',
'application compiled': 'application compilée',
'application is compiled and cannot be designed': "l'application est compilée et ne peut être désigné",
'application is compiled and cannot be designed': "l'application est compilée et ne peut être modifiée",
'arguments': 'arguments',
'back': 'retour',
'cache': 'cache',
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyé',
'cannot create file': 'ne peu pas créer de fichier',
'cannot upload file "%(filename)s"': 'ne peu pas charger le fichier "%(filename)s"',
'check all': 'tous vérifier ',
'click to check for upgrades': 'Cliquez pour vérifier les mises à niveau',
'cache, errors and sessions cleaned': 'cache, erreurs et sessions nettoyés',
'cannot create file': 'ne peut pas créer de fichier',
'cannot upload file "%(filename)s"': 'ne peut pas charger le fichier "%(filename)s"',
'check all': 'tout vérifier ',
'click to check for upgrades': 'Cliquez pour vérifier les mises jour',
'code': 'code',
'collapse/expand all': 'collapse/expand all',
'compiled application removed': 'application compilée enlevé',
'collapse/expand all': 'tout réduire/agrandir',
'compiled application removed': 'application compilée enlevée',
'controllers': 'contrôleurs',
'create file with filename:': 'créer un fichier avec nom de fichier:',
'create new application:': 'créer une nouvelle application:',
'created by': 'créé par',
'crontab': 'crontab',
'currently running': 'currently running',
'currently saved or': 'actuellement enregistrés ou',
'currently running': 'tourne actuellement',
'currently saved or': 'actuellement enregistré ou',
'data uploaded': 'données chargées',
'database': 'base de données',
'database %s select': 'base de données %s sélectionner',
'database administration': 'administration base de données',
'db': 'db',
'db': 'bdd',
'defines tables': 'définit les tables',
'delete': 'supprimer',
'delete all checked': 'supprimer tout ce qui est cocher',
'delete plugin': ' supprimer plugin',
'delete all checked': 'supprimer tout ce qui est coché',
'delete plugin': ' supprimer le plugin',
'design': 'conception',
'direction: ltr': 'direction: ltr',
'docs': 'docs',
'done!': 'fait!',
'download layouts': 'download layouts',
'download plugins': 'download plugins',
'download layouts': 'télécharger layouts',
'download plugins': 'télécharger plugins',
'edit controller': 'modifier contrôleur',
'edit views:': 'edit views:',
'export as csv file': 'exportation au format CSV',
'edit views:': 'modifier vues:',
'export as csv file': 'export au format CSV',
'exposes': 'expose',
'exposes:': 'exposes:',
'exposes:': 'expose:',
'extends': 'étend',
'failed to reload module': 'impossible de recharger le module',
'failed to reload module because:': 'failed to reload module because:',
'failed to reload module because:': 'impossible de recharger le module car:',
'file "%(filename)s" created': 'fichier "%(filename)s" créé',
'file "%(filename)s" deleted': 'fichier "%(filename)s" supprimé',
'file "%(filename)s" uploaded': 'fichier "%(filename)s" chargé',
@@ -200,7 +200,7 @@
'file does not exist': "fichier n'existe pas",
'file saved on %(time)s': 'fichier enregistré le %(time)s',
'file saved on %s': 'fichier enregistré le %s',
'filter': 'filter',
'filter': 'filtre',
'htmledit': 'edition html',
'includes': 'inclus',
'index': 'index',
@@ -219,14 +219,14 @@
'modules': 'modules',
'new application "%s" created': 'nouvelle application "%s" créée',
'new plugin installed': 'nouveau plugin installé',
'new record inserted': 'nouvelle entrée inséré',
'new record inserted': 'nouvelle entrée insérée',
'next 100 rows': '100 lignes suivantes',
'no match': 'no match',
'no match': 'aucune correspondance',
'or import from csv file': 'ou importer depuis un fichier CSV ',
'or provide app url:': 'or provide app url:',
'or provide app url:': "ou fournir l'URL de l'app:",
'or provide application url:': "ou fournir l'URL de l'application:",
'pack plugin': 'paquet plugin',
'password changed': 'password changed',
'password changed': 'mot de passe modifié',
'plugin "%(plugin)s" deleted': 'plugin "%(plugin)s" supprimé',
'plugins': 'plugins',
'previous 100 rows': '100 lignes précédentes',
@@ -245,9 +245,9 @@
'submit': 'envoyer',
'table': 'table',
'test': 'tester',
'the application logic, each URL path is mapped in one exposed function in the controller': "la logique de l'application, chaque route URL est mappé dans une fonction exposée dans le contrôleur",
'the data representation, define database tables and sets': 'la représentation des données, défini les tables de bases de données et sets',
'the presentations layer, views are also known as templates': 'la couche des présentations, les vues sont également connus en tant que modèles',
'the application logic, each URL path is mapped in one exposed function in the controller': "la logique de l'application, chaque chemin d'URL est mappé dans une fonction exposée dans le contrôleur",
'the data representation, define database tables and sets': 'La représentation des données, définir les tables et ensembles de la base de données',
'the presentations layer, views are also known as templates': 'la couche de présentation, les vues sont également appelées modèles',
'these files are served without processing, your images go here': 'ces fichiers sont servis sans transformation, vos images vont ici',
'to previous version.': 'à la version précédente.',
'translation strings for the application': "chaînes de traduction de l'application",
@@ -258,22 +258,22 @@
'unable to delete file plugin "%(plugin)s"': 'impossible de supprimer le plugin "%(plugin)s"',
'unable to parse csv file': "impossible d'analyser les fichiers CSV",
'unable to uninstall "%s"': 'impossible de désinstaller "%s"',
'unable to upgrade because "%s"': 'unable to upgrade because "%s"',
'unable to upgrade because "%s"': 'impossible de mettre à jour car "%s"',
'uncheck all': 'tout décocher',
'update': 'mettre à jour',
'update all languages': 'mettre à jour toutes les langues',
'upgrade now': 'upgrade now',
'upgrade web2py now': 'upgrade web2py now',
'upload': 'upload',
'upgrade now': 'mettre à jour maintenant',
'upgrade web2py now': 'mettre à jour web2py maintenant',
'upload': 'charger',
'upload application:': "charger l'application:",
'upload file:': 'charger le fichier:',
'upload plugin file:': 'charger fichier plugin:',
'user': 'user',
'user': 'utilisateur',
'variables': 'variables',
'versioning': 'versioning',
'view': 'vue',
'views': 'vues',
'web2py Recent Tweets': 'web2py Tweets récentes',
'web2py Recent Tweets': 'Tweets récents sur web2py ',
'web2py is up to date': 'web2py est à jour',
'web2py upgraded; please restart it': 'web2py upgraded; please restart it',
'web2py upgraded; please restart it': 'web2py mis à jour; veuillez le redémarrer',
}
+2 -1
View File
@@ -92,7 +92,7 @@ div.flash {
position:fixed;
padding:10px;
top:48px;
right:50px;
right:250px;
min-width:280px;
opacity:0.95;
margin:0px 0px 10px 10px;
@@ -191,6 +191,7 @@ div.error {
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
@@ -8,21 +8,28 @@ jQuery(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(){
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
if (wid>=980){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
};
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
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');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});
});
+35 -2
View File
@@ -3,6 +3,16 @@
{{
def shortcut(combo, description):
return XML('<li><span class="teletype-text">%s</span><span>%s</span></li>' % (combo, description))
def listfiles(app, dir, regexp='.*\.py$'):
files = sorted(
listdir(apath('%(app)s/%(dir)s/' % {'app':app, 'dir':dir}, r=request), regexp))
files = [x.replace('\\', '/') for x in files if not x.endswith('.bak')]
return files
def editfile(path,file,vars={}):
args=(path,file) if 'app' in vars else (app,path,file)
return A(file, _class='', _href=URL('edit', args=args, vars=vars), _style='word-wrap: break-word;')
}}
{{if TEXT_EDITOR == 'amy':}}
{{include 'default/amy_ajax.html'}}
@@ -128,8 +138,31 @@ jQuery(document).ready(function(){
<a class="button btn" href="http://www.web2py.com/examples/static/epydoc/index.html" target="_blank"><span>{{=T('docs')}}</span></a>
</p>
</div>
<div id="editor_area">
<form action="{{=URL('edit',args=filename)}}" method="post" name="editform" id="editform">
<div id="editor_area" class="row-fluid">
<ul class="nav nav-list span2 well" rel="pagebookmark">
{{dirs=[{'name':'models', 'reg':'.*\.py$'},
{'name':'controllers', 'reg':'.*\.py$'},
{'name':'views', 'reg':'[\w/\-]+(\.\w+)+$'},
{'name':'modules', 'reg':'.*\.py$'},
{'name':'private', 'reg': '[^\.#].*'}]}}
{{for dir in dirs:}}
<li class="nav-header component" onclick="collapse('{{="%s_files" % dir['name']}}');">{{=dir['name']}}</li>
<li id="{{="%s_files" % dir['name']}}">
<ul class="nav nav-list">
{{for f in listfiles(app, dir['name'], regexp=dir['reg'] ):}}
{{id="%s__" % dir['name'] + f.replace('.','__')}}
{{current_file = request.args(len(request.args) - 1)}}
<li class="{{='active' if current_file==f else ''}}">
{{=editfile(dir['name'], f, dict(id=id))}}
</li>
{{pass}}
</ul>
</li>
{{pass}}
</ul>
<form action="{{=URL('edit',args=filename)}}" method="post" name="editform" id="editform" class="span10">
<div class="editor-bar-column">
<label>{{=T('Save file:')}}</label>
<a value="save" name="save" onclick="return doClickSave();" class="icon saveicon" style="background-image: -webkit-linear-gradient(top,white,#E6E6E6);">
-2
View File
@@ -8,9 +8,7 @@
<title>{{=response.title or URL()}}</title>
{{
response.files.append(URL('static','css/bootstrap.min.css'))
#response.files.append(URL('static','css/styles.css'))
response.files.append(URL('static','css/bootstrap_essentials.css'))
# response.files.append(URL('static','css/bootstrap_adapters.css'))
response.files.append(URL('static','css/bootstrap-responsive.min.css'))
}}
{{include 'web2py_ajax.html'}}
+1 -1
View File
@@ -3,7 +3,7 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
//--></script>
{{
response.files.insert(0,URL('static','js/jquery.js'))
@@ -279,14 +279,15 @@ def update():
(db, table) = get_table(request)
keyed = hasattr(db[table], '_primarykey')
record = None
db[table]._common_filter = None
if keyed:
key = [f for f in request.vars if f in db[table]._primarykey]
if key:
record = db(db[table][key[0]] == request.vars[key[
0]], ignore_common_filters=True).select().first()
0]]).select().first()
else:
record = db(db[table].id == request.args(
2), ignore_common_filters=True).select().first()
2)).select().first()
if not record:
qry = query_by_table_type(table, db)
+2 -1
View File
@@ -92,7 +92,7 @@ div.flash {
position:fixed;
padding:10px;
top:48px;
right:50px;
right:250px;
min-width:280px;
opacity:0.95;
margin:0px 0px 10px 10px;
@@ -191,6 +191,7 @@ div.error {
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
@@ -8,21 +8,28 @@ jQuery(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(){
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
if (wid>=980){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
};
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
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');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});
});
+1 -1
View File
@@ -3,7 +3,7 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
//--></script>
{{
response.files.insert(0,URL('static','js/jquery.js'))
+3 -2
View File
@@ -279,14 +279,15 @@ def update():
(db, table) = get_table(request)
keyed = hasattr(db[table], '_primarykey')
record = None
db[table]._common_filter = None
if keyed:
key = [f for f in request.vars if f in db[table]._primarykey]
if key:
record = db(db[table][key[0]] == request.vars[key[
0]], ignore_common_filters=True).select().first()
0]]).select().first()
else:
record = db(db[table].id == request.args(
2), ignore_common_filters=True).select().first()
2)).select().first()
if not record:
qry = query_by_table_type(table, db)
+2 -1
View File
@@ -2,7 +2,7 @@
# this file is released under public domain and you can use without limitations
#########################################################################
## This is a samples controller
## This is a sample controller
## - index is the default action of any application
## - user is required for authentication and authorization
## - download is for downloading files uploaded in the db (does streaming)
@@ -39,6 +39,7 @@ def user():
return dict(form=auth())
@cache.action()
def download():
"""
allows downloading of uploaded files
+6 -6
View File
@@ -62,12 +62,12 @@
'Client IP': 'IP del Cliente',
'Community': 'Comunidad',
'compile': 'compilar',
'compiled application removed': 'aplicación compilada removida',
'compiled application removed': 'aplicación compilada eliminada',
'Components and Plugins': 'Componentes y Plugins',
'Controller': 'Controlador',
'Controllers': 'Controladores',
'controllers': 'controladores',
'Copyright': 'Derechos de autor',
'Copyright': 'Copyright',
'create file with filename:': 'cree archivo con nombre:',
'Create new application': 'Cree una nueva aplicación',
'create new application:': 'nombre de la nueva aplicación:',
@@ -122,7 +122,7 @@
'export as csv file': 'exportar como archivo CSV',
'exposes': 'expone',
'extends': 'extiende',
'failed to reload module': 'recarga del módulo ha fallado',
'failed to reload module': 'la recarga del módulo ha fallado',
'FAQ': 'FAQ',
'file "%(filename)s" created': 'archivo "%(filename)s" creado',
'file "%(filename)s" deleted': 'archivo "%(filename)s" eliminado',
@@ -142,7 +142,7 @@
'Hello World': 'Hola Mundo',
'help': 'ayuda',
'Home': 'Home',
'How did you get here?': '¿Cómo llegasta hasta allá?',
'How did you get here?': '¿Cómo llegaste aquí?',
'htmledit': 'htmledit',
'import': 'importar',
'Import/Export': 'Importar/Exportar',
@@ -228,7 +228,7 @@
'register': 'registrese',
'Registration key': 'Llave de Registro',
'remove compiled': 'eliminar compiladas',
'Reset Password key': 'Restaurar LLavel de la Contraseña',
'Reset Password key': 'Restaurar Llave de la Contraseña',
'Resolve Conflict file': 'archivo Resolución de Conflicto',
'restore': 'restaurar',
'revert': 'revertir',
@@ -260,7 +260,7 @@
'the application logic, each URL path is mapped in one exposed function in the controller': 'la lógica de la aplicación, cada ruta URL se mapea en una función expuesta en el controlador',
'The Core': 'El Núcleo',
'the data representation, define database tables and sets': 'la representación de datos, define tablas y conjuntos de base de datos',
'The output of the file is a dictionary that was rendered by the view %s': 'La salida del archivo es un diccionario escenificado por la vista %s',
'The output of the file is a dictionary that was rendered by the view %s': 'La salida de dicha función es un diccionario que es desplegado por la vista %s',
'the presentations layer, views are also known as templates': 'la capa de presentación, las vistas también son llamadas plantillas',
'The Views': 'Las Vistas',
'There are no controllers': 'No hay controladores',
+49 -44
View File
@@ -3,14 +3,14 @@
'!langcode!': 'fr',
'!langname!': 'Français',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" est une expression optionnelle comme "champ1=\'nouvellevaleur\'". Vous ne pouvez mettre à jour ou supprimer les résultats d\'un JOIN',
'%s %%{row} deleted': '%s rangées supprimées',
'%s %%{row} updated': '%s rangées mises à jour',
'%s %%{row} deleted': '%s lignes supprimées',
'%s %%{row} updated': '%s lignes mises à jour',
'%s selected': '%s sélectionné',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'About': 'À propos',
'Access Control': "Contrôle d'accès",
'Administrative Interface': 'Administrative Interface',
'Administrative Interface': "Interface d'administration",
'Administrative interface': "Interface d'administration",
'Ajax Recipes': 'Recettes Ajax',
'appadmin is disabled because insecure channel': "appadmin est désactivée parce que le canal n'est pas sécurisé",
@@ -20,38 +20,40 @@
'Buy this book': 'Acheter ce livre',
'cache': 'cache',
'Cache': 'Cache',
'Cache Keys': 'Cache Keys',
'Cache Keys': 'Clés de cache',
'Cannot be empty': 'Ne peut pas être vide',
'change password': 'changer le mot de passe',
'Check to delete': 'Cliquez pour supprimer',
'Check to delete:': 'Cliquez pour supprimer:',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Clear CACHE?': 'Vider le CACHE?',
'Clear DISK': 'Vider le DISQUE',
'Clear RAM': 'Vider la RAM',
'Client IP': 'IP client',
'Community': 'Communauté',
'Components and Plugins': 'Components and Plugins',
'Components and Plugins': 'Composants et Plugins',
'Controller': 'Contrôleur',
'Copyright': 'Copyright',
'Created By': 'Créé par',
'Created On': 'Créé le',
'Current request': 'Demande actuelle',
'Current response': 'Réponse actuelle',
'Current session': 'Session en cours',
'customize me!': 'personnalisez-moi!',
'data uploaded': 'données téléchargées',
'Database': 'base de données',
'Database %s select': 'base de données %s select',
'db': 'db',
'DB Model': 'Modèle DB',
'Database %s select': 'base de données %s selectionnée',
'db': 'bdd',
'DB Model': 'Modèle BDD',
'Delete:': 'Supprimer:',
'Demo': 'Démo',
'Deployment Recipes': 'Recettes de déploiement',
'Description': 'Description',
'design': 'design',
'DISK': 'DISK',
'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared',
'DISK': 'DISQUE',
'Disk Cache Keys': 'Clés de cache du disque',
'Disk Cleared': 'Disque vidé',
'Documentation': 'Documentation',
"Don't know what to do?": "Don't know what to do?",
"Don't know what to do?": 'Vous ne savez pas quoi faire?',
'done!': 'fait!',
'Download': 'Téléchargement',
'E-mail': 'E-mail',
@@ -59,8 +61,8 @@
'Edit current record': "Modifier l'enregistrement courant",
'edit profile': 'modifier le profil',
'Edit This App': 'Modifier cette application',
'Email and SMS': 'Email and SMS',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'Email and SMS': 'Email et SMS',
'enter an integer between %(min)g and %(max)g': 'entrez un entier entre %(min)g et %(max)g',
'Errors': 'Erreurs',
'export as csv file': 'exporter sous forme de fichier csv',
'FAQ': 'FAQ',
@@ -69,10 +71,10 @@
'Free Applications': 'Applications gratuites',
'Function disabled': 'Fonction désactivée',
'Group ID': 'Groupe ID',
'Groups': 'Groups',
'Groups': 'Groupes',
'Hello World': 'Bonjour le monde',
'Home': 'Accueil',
'How did you get here?': 'How did you get here?',
'How did you get here?': 'Comment êtes-vous arrivé ici?',
'import': 'import',
'Import/Export': 'Importer/Exporter',
'Index': 'Index',
@@ -83,48 +85,51 @@
'Invalid email': 'E-mail invalide',
'Invalid Query': 'Requête Invalide',
'invalid request': 'requête invalide',
'Key': 'Key',
'Is Active': 'Est actif',
'Key': 'Clé',
'Last name': 'Nom',
'Layout': 'Mise en page',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts',
'Live chat': 'Chat live',
'Live Chat': 'Live Chat',
'Layout Plugins': 'Plugins de mise en page',
'Layouts': 'Mises en page',
'Live chat': 'Chat en direct',
'Live Chat': 'Chat en direct',
'login': 'connectez-vous',
'Login': 'Connectez-vous',
'logout': 'déconnectez-vous',
'lost password': 'mot de passe perdu',
'Lost Password': 'Mot de passe perdu',
'Lost password?': 'Lost password?',
'Lost password?': 'Mot de passe perdu?',
'lost password?': 'mot de passe perdu?',
'Main Menu': 'Menu principal',
'Manage Cache': 'Manage Cache',
'Manage Cache': 'Gérer le Cache',
'Menu Model': 'Menu modèle',
'My Sites': 'My Sites',
'Modified By': 'Modifié par',
'Modified On': 'Modifié le',
'My Sites': 'Mes sites',
'Name': 'Nom',
'New Record': 'Nouvel enregistrement',
'new record inserted': 'nouvel enregistrement inséré',
'next 100 rows': '100 prochaines lignes',
'No databases in this application': "Cette application n'a pas de bases de données",
'Object or table name': 'Object or table name',
'Object or table name': 'Objet ou nom de table',
'Online examples': 'Exemples en ligne',
'or import from csv file': "ou importer d'un fichier CSV",
'Origin': 'Origine',
'Other Plugins': 'Other Plugins',
'Other Plugins': 'Autres Plugins',
'Other Recipes': 'Autres recettes',
'Overview': 'Présentation',
'Password': 'Mot de passe',
"Password fields don't match": 'Les mots de passe ne correspondent pas',
'Plugins': 'Plugiciels',
'Plugins': 'Plugins',
'Powered by': 'Alimenté par',
'Preface': 'Préface',
'previous 100 rows': '100 lignes précédentes',
'Python': 'Python',
'Query:': 'Requête:',
'Quick Examples': 'Examples Rapides',
'Quick Examples': 'Exemples Rapides',
'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared',
'RAM Cache Keys': 'Clés de cache de la RAM',
'Ram Cleared': 'Ram vidée',
'Readme': 'Lisez-moi',
'Recipes': 'Recettes',
'Record': 'enregistrement',
@@ -133,7 +138,7 @@
'Record id': "id d'enregistrement",
'Register': "S'inscrire",
'register': "s'inscrire",
'Registration identifier': 'Registration identifier',
'Registration identifier': "Identifiant d'enregistrement",
'Registration key': "Clé d'enregistrement",
'Remember me (for 30 days)': 'Se souvenir de moi (pendant 30 jours)',
'Request reset password': 'Demande de réinitialiser le mot clé',
@@ -144,42 +149,42 @@
'Rows selected': 'Lignes sélectionnées',
'Semantic': 'Sémantique',
'Services': 'Services',
'Size of cache:': 'Size of cache:',
'Size of cache:': 'Taille du cache:',
'state': 'état',
'Statistics': 'Statistics',
'Statistics': 'Statistiques',
'Stylesheet': 'Feuille de style',
'submit': 'submit',
'submit': 'soumettre',
'Submit': 'Soumettre',
'Support': 'Support',
'Sure you want to delete this object?': 'Êtes-vous sûr de vouloir supprimer cet objet?',
'Table': 'tableau',
'Table name': 'Nom du tableau',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "query" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': 'La "requête" est une condition comme "db.table1.champ1==\'valeur\'". Quelque chose comme "db.table1.champ1==db.table2.champ2" résulte en un JOIN SQL.',
'The Core': 'Le noyau',
'The output of the file is a dictionary that was rendered by the view %s': 'La sortie de ce fichier est un dictionnaire qui été restitué par la vue %s',
'The Views': 'Les Vues',
'This App': 'Cette Appli',
'This is a copy of the scaffolding application': "Ceci est une copie de l'application échafaudage",
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'Time in Cache (h:m:s)': 'Temps en Cache (h:m:s)',
'Timestamp': 'Horodatage',
'Twitter': 'Twitter',
'unable to parse csv file': "incapable d'analyser le fichier cvs",
'Update:': 'Mise à jour:',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT pour construire des requêtes plus complexes.',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': 'Employez (...)&(...) pour AND, (...)|(...) pour OR, and ~(...) pour NOT afin de construire des requêtes plus complexes.',
'User %(id)s Logged-in': 'Utilisateur %(id)s connecté',
'User %(id)s Registered': 'Utilisateur %(id)s enregistré',
'User ID': 'ID utilisateur',
'User Voice': 'User Voice',
'User Voice': "Voix de l'utilisateur",
'Verify Password': 'Vérifiez le mot de passe',
'Videos': 'Vidéos',
'View': 'Présentation',
'Web2py': 'Web2py',
'Welcome': 'Bienvenu',
'Welcome': 'Bienvenue',
'Welcome %s': 'Bienvenue %s',
'Welcome to web2py': 'Bienvenue à web2py',
'Welcome to web2py!': 'Welcome to web2py!',
'Welcome to web2py!': 'Bienvenue à web2py!',
'Which called the function %s located in the file %s': 'Qui a appelé la fonction %s se trouvant dans le fichier %s',
'You are successfully running web2py': 'Vous roulez avec succès web2py',
'You are successfully running web2py': 'Vous exécutez avec succès web2py',
'You can modify this application and adapt it to your needs': "Vous pouvez modifier cette application et l'adapter à vos besoins",
'You visited the url %s': "Vous avez visité l'URL %s",
}
+272
View File
@@ -0,0 +1,272 @@
# coding: utf8
{
'!langcode!': 'id',
'!langname!': 'Indonesian',
'%d days ago': '%d hari yang lalu',
'%d hours ago': '%d jam yang lalu',
'%d minutes ago': '%d menit yang lalu',
'%d months ago': '%d bulan yang lalu',
'%d seconds ago': '%d detik yang lalu',
'%d seconds from now': '%d detik dari sekarang',
'%d weeks ago': '%d minggu yang lalu',
'%d years ago': '%d tahun yang lalu',
'%s %%{row} deleted': '%s %%{row} dihapus',
'%s %%{row} updated': '%s %%{row} diperbarui',
'%s selected': '%s dipilih',
'%Y-%m-%d': '%d-%m-%Y',
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
'(requires internet access, experimental)': '(membutuhkan akses internet, eksperimental)',
'(something like "it-it")': '(sesuatu seperti "it-it")',
'1 day ago': '1 hari yang lalu',
'1 hour ago': '1 jam yang lalu',
'1 minute ago': '1 menit yang lalu',
'1 month ago': '1 bulan yang lalu',
'1 second ago': '1 detik yang lalu',
'1 week ago': '1 minggu yang lalu',
'1 year ago': '1 tahun yang lalu',
'< Previous': '< Sebelumnya',
'About': 'Tentang',
'About application': 'Tentang Aplikasi',
'Add': 'Tambah',
'Additional code for your application': 'Tambahan kode untuk aplikasi Anda',
'Address': 'Alamat',
'Admin language': 'Bahasa Admin',
'administrative interface': 'antarmuka administrative',
'Administrator Password:': 'Administrator Kata Sandi:',
'Ajax Recipes': 'Resep Ajax',
'An error occured, please %s the page': 'Terjadi kesalahan, silakan %s halaman',
'And': 'Dan',
'and rename it:': 'dan memberi nama baru itu:',
'Answer': 'Jawaban',
'appadmin is disabled because insecure channel': 'AppAdmin dinonaktifkan karena kanal tidak aman',
'application "%s" uninstalled': 'applikasi "%s" dihapus',
'application compiled': 'aplikasi dikompilasi',
'Application name:': 'Nama Applikasi:',
'are not used yet': 'tidak digunakan lagi',
'Are you sure you want to delete this object?': 'Apakah Anda yakin ingin menghapus ini?',
'Are you sure you want to uninstall application "%s"?': 'Apakah Anda yakin ingin menghapus aplikasi "%s"?',
'Available Databases and Tables': 'Database dan Tabel yang tersedia',
'Back': 'Kembali',
'Buy this book': 'Beli buku ini',
'cache, errors and sessions cleaned': 'cache, kesalahan dan sesi dibersihkan',
'can be a git repo': 'bisa menjadi repo git',
'Cancel': 'Batalkan',
'Cannot be empty': 'Tidak boleh kosong',
'Change admin password': 'Ubah kata sandi admin',
'Change password': 'Ubah kata sandi',
'Check for upgrades': 'Periksa upgrade',
'Check to delete': 'Centang untuk menghapus',
'Checking for upgrades...': 'Memeriksa untuk upgrade...',
'Clean': 'Bersih',
'Clear': 'Hapus',
'Clear CACHE?': 'Hapus CACHE?',
'Clear DISK': 'Hapus DISK',
'Clear RAM': 'Hapus RAM',
'Click row to expand traceback': 'Klik baris untuk memperluas traceback',
'Close': 'Tutup',
'collapse/expand all': 'kempis / memperluas semua',
'Community': 'Komunitas',
'Compile': 'Kompilasi',
'compiled application removed': 'aplikasi yang dikompilasi dihapus',
'Components and Plugins': 'Komponen dan Plugin',
'contains': 'mengandung',
'Controllers': 'Kontrolir',
'controllers': 'kontrolir',
'Copyright': 'Hak Cipta',
'Count': 'Hitung',
'Create': 'Buat',
'create file with filename:': 'buat file dengan nama:',
'created by': 'dibuat oleh',
'CSV (hidden cols)': 'CSV (kolom tersembunyi)',
'currently running': 'sedang berjalan',
'data uploaded': 'data diunggah',
'Database %s select': 'Memilih Database %s',
'database administration': 'administrasi database',
'defines tables': 'mendefinisikan tabel',
'Delete': 'Hapus',
'delete all checked': 'menghapus semua yang di centang',
'Delete this file (you will be asked to confirm deletion)': 'Hapus file ini (Anda akan diminta untuk mengkonfirmasi penghapusan)',
'Delete:': 'Hapus:',
'Description': 'Keterangan',
'design': 'disain',
'direction: ltr': 'petunjuk: ltr',
'Disk Cleared': 'Disk Dihapus',
'Documentation': 'Dokumentasi',
"Don't know what to do?": 'Tidak tahu apa yang harus dilakukan?',
'done!': 'selesai!',
'Download': 'Unduh',
'Download .w2p': 'Unduh .w2p',
'download layouts': 'unduh layouts',
'download plugins': 'unduh plugins',
'Duration': 'Durasi',
'Edit': 'Mengedit',
'Edit application': 'Mengedit Aplikasi',
'Email sent': 'Email dikirim',
'enter a valid email address': 'masukkan alamat email yang benar',
'enter a valid URL': 'masukkan URL yang benar',
'enter a value': 'masukkan data',
'Error': 'Kesalahan',
'Error logs for "%(app)s"': 'Catatan kesalahan untuk "%(app)s"',
'Errors': 'Kesalahan',
'export as csv file': 'ekspor sebagai file csv',
'Export:': 'Ekspor:',
'exposes': 'menghadapkan',
'extends': 'meluaskan',
'filter': 'menyaring',
'First Name': 'Nama Depan',
'Forgot username?': 'Lupa nama pengguna?',
'Free Applications': 'Aplikasi Gratis',
'Gender': 'Jenis Kelamin',
'Group %(group_id)s created': 'Grup %(group_id)s dibuat',
'Group uniquely assigned to user %(id)s': 'Grup unik yang diberikan kepada pengguna %(id)s',
'Groups': 'Grup',
'Guest': 'Tamu',
'Hello World': 'Halo Dunia',
'Help': 'Bantuan',
'Home': 'Halaman Utama',
'How did you get here?': 'Bagaimana kamu bisa di sini?',
'Image': 'Gambar',
'import': 'impor',
'Import/Export': 'Impor/Ekspor',
'includes': 'termasuk',
'Install': 'Memasang',
'Installation': 'Instalasi',
'Installed applications': 'Aplikasi yang diinstal',
'Introduction': 'Pengenalan',
'Invalid email': 'Email tidak benar',
'Language': 'Bahasa',
'languages': 'bahasa',
'Languages': 'Bahasa',
'Last Name': 'Nama Belakang',
'License for': 'Lisensi untuk',
'loading...': 'sedang memuat...',
'Logged in': 'Masuk',
'Logged out': 'Keluar',
'Login': 'Masuk',
'Login to the Administrative Interface': 'Masuk ke antarmuka Administrasi',
'Logout': 'Keluar',
'Lost Password': 'Lupa Kata Sandi',
'Lost password?': 'Lupa kata sandi?',
'Maintenance': 'Pemeliharaan',
'Manage': 'Mengelola',
'Manage Cache': 'Mengelola Cache',
'models': 'model',
'Models': 'Model',
'Modules': 'Modul',
'modules': 'modul',
'My Sites': 'Situs Saya',
'New': 'Baru',
'new application "%s" created': 'aplikasi baru "%s" dibuat',
'New password': 'Kata sandi baru',
'New simple application': 'Aplikasi baru sederhana',
'News': 'Berita',
'next 100 rows': '100 baris berikutnya',
'Next >': 'Berikutnya >',
'Next Page': 'Halaman Berikutnya',
'No databases in this application': 'Tidak ada database dalam aplikasi ini',
'No ticket_storage.txt found under /private folder': 'Tidak ditemukan ticket_storage.txt dalam folder /private',
'not a Zip Code': 'bukan Kode Pos',
'Note': 'Catatan',
'Old password': 'Kata sandi lama',
'Online examples': 'Contoh Online',
'Or': 'Atau',
'or alternatively': 'atau alternatif',
'Or Get from URL:': 'Atau Dapatkan dari URL:',
'or import from csv file': 'atau impor dari file csv',
'Other Plugins': 'Plugin Lainnya',
'Other Recipes': 'Resep Lainnya',
'Overview': 'Ikhtisar',
'Overwrite installed app': 'Ikhtisar app yang terinstall',
'Pack all': 'Pak semua',
'Pack compiled': 'Pak yang telah dikompilasi',
'Pack custom': 'Pak secara kustomisasi',
'Password': 'Kata sandi',
'Password changed': 'Kata sandi berubah',
"Password fields don't match": 'Kata sandi tidak sama',
'please input your password again': 'silahkan masukan kata sandi anda lagi',
'plugins': 'plugin',
'Plugins': 'Plugin',
'Plural-Forms:': 'Bentuk-Jamak:',
'Powered by': 'Didukung oleh',
'Preface': 'Pendahuluan',
'previous 100 rows': '100 baris sebelumnya',
'Previous Page': 'Halaman Sebelumnya',
'private files': 'file pribadi',
'Private files': 'File pribadi',
'Profile': 'Profil',
'Profile updated': 'Profil diperbarui',
'Project Progress': 'Perkembangan Proyek',
'Quick Examples': 'Contoh Cepat',
'Ram Cleared': 'Ram Dihapus',
'Recipes': 'Resep',
'Register': 'Daftar',
'Registration successful': 'Pendaftaran berhasil',
'reload': 'memuat kembali',
'Reload routes': 'Memuat rute kembali',
'Remember me (for 30 days)': 'Ingat saya (selama 30 hari)',
'Remove compiled': 'Hapus Kompilasi',
'Request reset password': 'Meminta reset kata sandi',
'Rows in Table': 'Baris dalam Tabel',
'Rows selected': 'Baris dipilih',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Jalankan tes di file ini (untuk menjalankan semua file, Anda juga dapat menggunakan tombol berlabel 'test')",
'Running on %s': 'Berjalan di %s',
'Save model as...': 'Simpan model sebagai ...',
'Save profile': 'Simpan profil',
'Search': 'Cari',
'Select Files to Package': 'Pilih Berkas untuk Paket',
'Send Email': 'Kirim Email',
'Service': 'Layanan',
'Site': 'Situs',
'Size of cache:': 'Ukuran cache:',
'starts with': 'dimulai dengan',
'static': 'statis',
'Static': 'Statis',
'Statistics': 'Statistik',
'Support': 'Mendukung',
'Table': 'Tabel',
'test': 'tes',
'The application logic, each URL path is mapped in one exposed function in the controller': 'Logika aplikasi, setiap jalur URL dipetakan dalam satu fungsi terpapar di kontrolir',
'The data representation, define database tables and sets': 'Representasi data, mendefinisikan tabel database dan set',
'There are no plugins': 'Tidak ada plugin',
'There are no private files': 'Tidak ada file pribadi',
'These files are not served, they are only available from within your app': 'File-file ini tidak dilayani, mereka hanya tersedia dari dalam aplikasi Anda',
'These files are served without processing, your images go here': 'File-file ini disajikan tanpa pengolahan, gambar Anda di sini',
'This App': 'App Ini',
'Time in Cache (h:m:s)': 'Waktu di Cache (h: m: s)',
'To create a plugin, name a file/folder plugin_[name]': 'Untuk membuat sebuah plugin, nama file / folder plugin_ [nama]',
'too short': 'terlalu pendek',
'Translation strings for the application': 'Terjemahan string untuk aplikasi',
'Try the mobile interface': 'Coba antarmuka ponsel',
'Unable to download because:': 'Tidak dapat mengunduh karena:',
'unable to parse csv file': 'tidak mampu mengurai file csv',
'update all languages': 'memperbarui semua bahasa',
'Update:': 'Perbarui:',
'Upload': 'Unggah',
'Upload a package:': 'Unggah sebuah paket:',
'Upload and install packed application': 'Upload dan pasang aplikasi yang dikemas',
'upload file:': 'unggah file:',
'upload plugin file:': 'unggah file plugin:',
'User %(id)s Logged-in': 'Pengguna %(id)s Masuk',
'User %(id)s Logged-out': 'Pengguna %(id)s Keluar',
'User %(id)s Password changed': 'Pengguna %(id)s Kata Sandi berubah',
'User %(id)s Password reset': 'Pengguna %(id)s Kata Sandi telah direset',
'User %(id)s Profile updated': 'Pengguna %(id)s Profil diperbarui',
'User %(id)s Registered': 'Pengguna %(id)s Terdaftar',
'value already in database or empty': 'data sudah ada dalam database atau kosong',
'value not allowed': 'data tidak benar',
'value not in database': 'data tidak ada dalam database',
'Verify Password': 'Verifikasi Kata Sandi',
'Version': 'Versi',
'View': 'Lihat',
'Views': 'Lihat',
'views': 'lihat',
'Web Framework': 'Kerangka Web',
'web2py is up to date': 'web2py terbaru',
'web2py Recent Tweets': 'Tweet web2py terbaru',
'Website': 'Situs Web',
'Welcome': 'Selamat Datang',
'Welcome to web2py!': 'Selamat Datang di web2py!',
'You are successfully running web2py': 'Anda berhasil menjalankan web2py',
'You can modify this application and adapt it to your needs': 'Anda dapat memodifikasi aplikasi ini dan menyesuaikan dengan kebutuhan Anda',
'You visited the url %s': 'Anda mengunjungi url %s',
}
+217
View File
@@ -0,0 +1,217 @@
# coding: utf8
{
'!langcode!': 'my',
'!langname!': 'Malay',
'%d days ago': '%d hari yang lalu',
'%d hours ago': '%d jam yang lalu',
'%d minutes ago': '%d minit yang lalu',
'%d months ago': '%d bulan yang lalu',
'%d seconds ago': '%d saat yang lalu',
'%d seconds from now': '%d saat dari sekarang',
'%d weeks ago': '%d minggu yang lalu',
'%d years ago': '%d tahun yang lalu',
'%s %%{row} deleted': '%s %%{row} dihapuskan',
'%s %%{row} updated': '%s %%{row} dikemas kini',
'%s selected': '%s dipilih',
'%Y-%m-%d': '%d-%m-%Y',
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
'(requires internet access, experimental)': '(memerlukan akses internet, percubaan)',
'(something like "it-it")': '(sesuatu seperti "it-it")',
'1 day ago': '1 hari yang lalu',
'1 hour ago': '1 jam yang lalu',
'1 minute ago': '1 minit yang lalu',
'1 month ago': '1 bulan yang lalu',
'1 second ago': '1 saat yang lalu',
'1 week ago': '1 minggu yang lalu',
'1 year ago': '1 tahun yang lalu',
'< Previous': '< Sebelumnya',
'About': 'Mengenai',
'Add': 'Tambah',
'Admin language': 'Bahasa admin',
'Administrator Password:': 'Kata laluan Administrator:',
'Ajax Recipes': 'Resipi Ajax',
'An error occured, please %s the page': 'Kesilapan telah berlaku, sila %s laman',
'And': 'Dan',
'and rename it:': 'dan menamakan itu:',
'are not used yet': 'tidak digunakan lagi',
'Are you sure you want to delete this object?': 'Apakah anda yakin anda mahu memadam ini?',
'Back': 'Kembali',
'Buy this book': 'Beli buku ini',
'cache, errors and sessions cleaned': 'cache, kesilapan dan sesi dibersihkan',
'Cancel': 'Batal',
'Cannot be empty': 'Tidak boleh kosong',
'Change admin password': 'Tukar kata laluan admin',
'Change password': 'Tukar kata laluan',
'Clean': 'Bersihkan',
'Clear': 'Hapus',
'Clear CACHE?': 'Hapus CACHE?',
'Clear DISK': 'Hapus DISK',
'Clear RAM': 'Hapus RAM',
'Click row to expand traceback': 'Klik baris untuk mengembangkan traceback',
'Close': 'Tutup',
'Community': 'Komuniti',
'Components and Plugins': 'Komponen dan Plugin',
'contains': 'mengandung',
'Copyright': 'Hak Cipta',
'Create': 'Buat',
'create file with filename:': 'mencipta fail dengan nama:',
'created by': 'dicipta oleh',
'currently running': 'sedang berjalan',
'data uploaded': 'data diunggah',
'Delete': 'Hapus',
'Delete this file (you will be asked to confirm deletion)': 'Padam fail ini (anda akan diminta untuk mengesahkan pemadaman)',
'Delete:': 'Hapus:',
'design': 'disain',
'direction: ltr': 'arah: ltr',
'Disk Cleared': 'Disk Dihapuskan',
'Documentation': 'Dokumentasi',
"Don't know what to do?": 'Tidak tahu apa yang perlu dilakukan?',
'done!': 'selesai!',
'Download': 'Unduh',
'Duration': 'Tempoh',
'Email : ': 'Emel : ',
'Email sent': 'Emel dihantar',
'enter a valid email address': 'masukkan alamat emel yang benar',
'enter a valid URL': 'masukkan URL yang benar',
'enter a value': 'masukkan data',
'Error': 'Kesalahan',
'Errors': 'Kesalahan',
'export as csv file': 'eksport sebagai file csv',
'Export:': 'Eksport:',
'File': 'Fail',
'filter': 'menapis',
'First Name': 'Nama Depan',
'Forgot username?': 'Lupa nama pengguna?',
'Free Applications': 'Aplikasi Percuma',
'Gender': 'Jenis Kelamin',
'Group %(group_id)s created': 'Kumpulan %(group_id)s dicipta',
'Group uniquely assigned to user %(id)s': 'Kumpulan unik yang diberikan kepada pengguna %(id)s',
'Groups': 'Kumpulan',
'Hello World': 'Halo Dunia',
'Help': 'Bantuan',
'Home': 'Laman Utama',
'How did you get here?': 'Bagaimana kamu boleh di sini?',
'Image': 'Gambar',
'import': 'import',
'Import/Export': 'Import/Eksport',
'includes': 'termasuk',
'Install': 'Pasang',
'Installation': 'Pemasangan',
'Introduction': 'Pengenalan',
'Invalid email': 'Emel tidak benar',
'Language': 'Bahasa',
'languages': 'bahasa',
'Languages': 'Bahasa',
'Last Name': 'Nama Belakang',
'License for': 'lesen untuk',
'loading...': 'sedang memuat...',
'Logged in': 'Masuk',
'Logged out': 'Keluar',
'Login': 'Masuk',
'Logout': 'Keluar',
'Lost Password': 'Lupa Kata Laluan',
'Lost password?': 'Lupa kata laluan?',
'Maintenance': 'Penyelenggaraan',
'Manage': 'Menguruskan',
'Manage Cache': 'Menguruskan Cache',
'models': 'model',
'Models': 'Model',
'Modules': 'Modul',
'modules': 'modul',
'My Sites': 'Laman Saya',
'New': 'Baru',
'New password': 'Kata laluan baru',
'next 100 rows': '100 baris seterusnya',
'Next >': 'Seterusnya >',
'Next Page': 'Laman Seterusnya',
'No ticket_storage.txt found under /private folder': 'Ticket_storage.txt tidak dijumpai di bawah folder /private',
'not a Zip Code': 'bukan Pos',
'Old password': 'Kata laluan lama',
'Online examples': 'Contoh Online',
'Or': 'Atau',
'or alternatively': 'atau sebagai alternatif',
'Or Get from URL:': 'Atau Dapatkan dari URL:',
'or import from csv file': 'atau import dari file csv',
'Other Plugins': 'Plugin Lain',
'Other Recipes': 'Resipi Lain',
'Overview': 'Tinjauan',
'Pack all': 'Mengemaskan semua',
'Password': 'Kata laluan',
'Password changed': 'Kata laluan berubah',
"Password fields don't match": 'Kata laluan tidak sama',
'please input your password again': 'sila masukan kata laluan anda lagi',
'plugins': 'plugin',
'Plugins': 'Plugin',
'Powered by': 'Disokong oleh',
'Preface': 'Pendahuluan',
'previous 100 rows': '100 baris sebelumnya',
'Previous Page': 'Laman Sebelumnya',
'private files': 'fail peribadi',
'Private files': 'Fail peribadi',
'Profile': 'Profil',
'Profile updated': 'Profil dikemaskini',
'Project Progress': 'Kemajuan Projek',
'Quick Examples': 'Contoh Cepat',
'Ram Cleared': 'Ram Dihapuskan',
'Recipes': 'Resipi',
'Register': 'Daftar',
'Registration successful': 'Pendaftaran berjaya',
'reload': 'memuat kembali',
'Reload routes': 'Memuat laluan kembali',
'Remember me (for 30 days)': 'Ingat saya (selama 30 hari)',
'Request reset password': 'Meminta reset kata laluan',
'Rows selected': 'Baris dipilih',
'Running on %s': 'Berjalan pada %s',
'Save model as...': 'Simpan model sebagai ...',
'Save profile': 'Simpan profil',
'Search': 'Cari',
'Select Files to Package': 'Pilih Fail untuk Pakej',
'Send Email': 'Kirim Emel',
'Size of cache:': 'Saiz cache:',
'Solution': 'Penyelesaian',
'starts with': 'bermula dengan',
'static': 'statik',
'Static': 'Statik',
'Statistics': 'Statistik',
'Support': 'Menyokong',
'test': 'ujian',
'There are no plugins': 'Tiada plugin',
'There are no private files': 'Tiada fail peribadi',
'These files are not served, they are only available from within your app': 'Fail-fail ini tidak disampaikan, mereka hanya boleh didapati dari dalam aplikasi anda',
'These files are served without processing, your images go here': 'Ini fail disampaikan tanpa pemprosesan, imej anda di sini',
'This App': 'App Ini',
'Time in Cache (h:m:s)': 'Waktu di Cache (h: m: s)',
'Title': 'Judul',
'To create a plugin, name a file/folder plugin_[name]': 'Untuk mencipta plugin, nama fail/folder plugin_ [nama]',
'too short': 'terlalu pendek',
'Unable to download because:': 'Tidak dapat memuat turun kerana:',
'unable to parse csv file': 'tidak mampu mengurai file csv',
'update all languages': 'mengemaskini semua bahasa',
'Update:': 'Kemas kini:',
'Upgrade': 'Menaik taraf',
'Upload': 'Unggah',
'Upload a package:': 'Unggah pakej:',
'upload file:': 'unggah fail:',
'upload plugin file:': 'unggah fail plugin:',
'User %(id)s Logged-in': 'Pengguna %(id)s Masuk',
'User %(id)s Logged-out': 'Pengguna %(id)s Keluar',
'User %(id)s Password changed': 'Pengguna %(id)s Kata Laluan berubah',
'User %(id)s Password reset': 'Pengguna %(id)s Kata Laluan telah direset',
'User %(id)s Profile updated': 'Pengguna %(id)s Profil dikemaskini',
'User %(id)s Registered': 'Pengguna %(id)s Didaftarkan',
'value not allowed': 'data tidak benar',
'Verify Password': 'Pengesahan Kata Laluan',
'Version': 'Versi',
'Versioning': 'Pembuatan Sejarah',
'View': 'Lihat',
'Views': 'Lihat',
'views': 'Lihat',
'Web Framework': 'Rangka Kerja Web',
'web2py Recent Tweets': 'Tweet terbaru web2py',
'Website': 'Laman Web',
'Welcome': 'Selamat Datang',
'Welcome to web2py!': 'Selamat Datang di web2py!',
'You are successfully running web2py': 'Anda berjaya menjalankan web2py',
'You can modify this application and adapt it to your needs': 'Anda boleh mengubah suai aplikasi ini dan menyesuaikan dengan keperluan anda',
'You visited the url %s': 'Anda melawat url %s',
}
+129
View File
@@ -0,0 +1,129 @@
# coding: utf8
{
'!langcode!': 'tr',
'!langname!': 'Türkçe',
'%s %%(shop)': '%s %%(shop)',
'%s %%(shop[0])': '%s %%(shop[0])',
'%s %%{quark[0]}': '%s %%{quark[0]}',
'%s %%{shop[0]}': '%s %%{shop[0]}',
'%s %%{shop}': '%s %%{shop}',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'@markmin\x01**Hello World**': '**Merhaba Dünya**',
'About': 'Hakkında',
'Access Control': 'Erişim Denetimi',
'Administrative Interface': 'Yönetim Arayüzü',
'Ajax Recipes': 'Ajax Tarifleri',
'An error occured, please %s the page': 'Bir hata meydana geldi, lütfen sayfayı %s',
'Are you sure you want to delete this object?': 'Bu nesneyi silmek istediğinden emin misin?',
'Buy this book': 'Bu kitabı satın alın',
'Cannot be empty': 'Boş bırakılamaz',
'Check to delete': 'Silmek için denetle',
'Client IP': ' IP',
'Community': 'Topluluk',
'Components and Plugins': 'Bileşenler ve Eklentiler',
'Controller': 'Denetçi',
'Copyright': 'Telif',
'Created By': 'Tasarlayan',
'Created On': 'Oluşturma tarihi',
'customize me!': 'burayı değiştir!',
'Database': 'Veritabanı',
'DB Model': 'DB Model',
'Demo': 'Demo',
'Deployment Recipes': 'Yayınlama tarifleri',
'Description': 'Açıklama',
'Documentation': 'Kitap',
"Don't know what to do?": 'Neleri nasıl yapacağını bilmiyor musun?',
'Download': 'İndir',
'E-mail': 'E-posta',
'Email and SMS': 'E-posta ve kısa mesaj (SMS)',
'enter an integer between %(min)g and %(max)g': '%(min)g ve %(max)g arasında bir sayı girin',
'enter date and time as %(format)s': 'tarih ve saati %(format)s biçiminde girin',
'Errors': 'Hatalar',
'FAQ': 'SSS',
'First name': 'Ad',
'Forgot username?': 'Kullanıcı adını mı unuttun?',
'Forms and Validators': 'Biçimler ve Doğrulayıcılar',
'Free Applications': 'Ücretsiz uygulamalar',
'Giriş': 'Giriş',
'Group %(group_id)s created': '%(group_id)s takımı oluşturuldu',
'Group ID': 'Takım ID',
'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
'Groups': 'Topluluklar',
'Hello World': 'Merhaba Dünyalı',
'Hello World ## comment': 'Merhaba Dünyalı ',
'Hello World## comment': 'Merhaba Dünyalı',
'Home': 'Anasayfa',
'How did you get here?': 'Bu sayfayı görüntüleme uğruna neler mi oldu?',
'Introduction': 'Giriş',
'Invalid email': 'Yanlış eposta',
'Is Active': 'Etkin',
'Kayıt ol': 'Kayıt ol',
'Last name': 'Soyad',
'Layout': 'Şablon',
'Layout Plugins': 'Şablon Eklentileri',
'Layouts': 'Şablonlar',
'Live Chat': 'Canlı Sohbet',
'Logged in': 'Giriş yapıldı',
'Logged out': 'Çıkış yapıldı',
'Login': 'Giriş',
'Logout': 'Terket',
'Lost Password': 'Şifremi unuttum',
'Lost password?': 'Şifrenizimi unuttunuz?',
'Menu Model': 'Menu Model',
'Modified By': 'Değiştiren',
'Modified On': 'Değiştirilme tarihi',
'My Sites': 'Sitelerim',
'Name': 'İsim',
'Object or table name': 'Nesne ya da tablo adı',
'Online examples': 'Canlı örnekler',
'Origin': 'Origin',
'Other Plugins': 'Diğer eklentiler',
'Other Recipes': 'Diğer Tarifler',
'Overview': 'Göz gezdir',
'Password': 'Şifre',
"Password fields don't match": 'Şifreler uyuşmuyor',
'please input your password again': 'lütfen şifrenizi tekrar girin',
'Plugins': 'Eklentiler',
'Powered by': 'Yazılım Temeli',
'Preface': 'Preface',
'Profile': 'Hakkımda',
'Python': 'Python',
'Quick Examples': 'Hızlı Örnekler',
'Recipes': 'Recipes',
'Record ID': 'Kayıt ID',
'Register': 'Kayıt ol',
'Registration identifier': 'Registration identifier',
'Registration key': 'Kayıt anahtarı',
'Registration successful': 'Kayıt başarılı',
'reload': 'reload',
'Remember me (for 30 days)': 'Beni hatırla (30 gün)',
'Request reset password': 'Şifre sıfırla',
'Reset Password key': 'Şifre anahtarını sıfırla',
'Role': 'Role',
'Semantic': 'Semantik',
'Services': 'Hizmetler',
'Stylesheet': 'Stil Şablonu',
'Support': 'Destek',
'The Core': 'Çekirdek',
'The output of the file is a dictionary that was rendered by the view %s': 'Son olarak fonksiyonların vs. işlenip %s dosyasıyla tasarıma yedirilmesiyle sayfayı görüntüledin',
'The Views': 'The Views',
'This App': 'Bu Uygulama',
'Timestamp': 'Zaman damgası',
'Twitter': 'Twitter',
'User %(id)s Logged-in': '%(id)s Giriş yaptı',
'User %(id)s Logged-out': '%(id)s çıkış yaptı',
'User %(id)s Password reset': 'User %(id)s Password reset',
'User %(id)s Registered': '%(id)s Kayıt oldu',
'User ID': 'Kullanıcı ID',
'value already in database or empty': 'değer boş ya da veritabanında zaten mevcut',
'Verify Password': 'Şifreni Onayla',
'Videos': 'Videolar',
'View': 'View',
'Welcome': 'Hoşgeldin',
'Welcome to web2py!': 'Welcome to web2py!',
'Which called the function %s located in the file %s': 'Bu ziyaretle %s fonksiyonunu %s dosyasından çağırmış oldun ',
'You are successfully running web2py': 'web2py çatısını çalıştırmayı başardın',
'You can modify this application and adapt it to your needs': 'Artık uygulamayı kafana göre düzenleyebilirsin!',
'You visited the url %s': '%s adresini ziyaret ettin',
}
+244
View File
@@ -0,0 +1,244 @@
# coding: utf8
{
'!langcode!': 'zh-cn',
'!langname!': '中文',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"update" 应为选择表达式, 格式如 "field1=\'value\'". 但是对 JOIN 的结果不可以使用 update 或者 delete"',
'%s %%{row} deleted': '已删除 %s',
'%s %%{row} updated': '已更新 %s',
'%s selected': '%s 已选择',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(something like "it-it")': '(格式类似 "zh-tw")',
'A new version of web2py is available': '新版 web2py 已推出',
'A new version of web2py is available: %s': '新版 web2py 已推出: %s',
'about': '关于',
'About': '关于',
'About application': '关于本应用程序',
'Access Control': 'Access Control',
'Admin is disabled because insecure channel': '管理功能(Admin)在非安全连接环境下自动关闭',
'Admin is disabled because unsecure channel': '管理功能(Admin)在非安全连接环境下自动关闭',
'Administrative Interface': 'Administrative Interface',
'Administrative interface': '点击进入管理界面',
'Administrator Password:': '管理员密码:',
'Ajax Recipes': 'Ajax Recipes',
'An error occured, please %s the page': 'An error occured, please %s the page',
'appadmin is disabled because insecure channel': '管理界面在非安全通道下被禁用',
'Are you sure you want to delete file "%s"?': '确定要删除文件"%s"?',
'Are you sure you want to delete this object?': '确定要删除该对象么?',
'Are you sure you want to uninstall application "%s"': '确定要删除应用程序 "%s"',
'Are you sure you want to uninstall application "%s"?': '确定要删除应用程序 "%s"',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登录管理账号需要安全连接(HTTPS)或是在本地连接(localhost).',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因为在测试模式不保证多线程安全性,所以不可同时执行多个测试案例',
'ATTENTION: you cannot edit the running application!': '注意:不可编辑正在执行的应用程序!',
'Authentication': '验证',
'Available Databases and Tables': '可提供的数据库和数据表',
'Buy this book': '购买本书',
'cache': '高速缓存',
'Cache': 'Cache',
'Cache Keys': 'Cache Keys',
'Cannot be empty': '不可空白',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '编译失败:应用程序有错误,请排除错误后再尝试编译.',
'Change Password': '修改密码',
'change password': '修改密码',
'Check to delete': '打勾以示删除',
'Check to delete:': '打勾以示删除:',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Client IP': '客户端网址(IP)',
'Community': 'Community',
'Components and Plugins': 'Components and Plugins',
'Controller': '控件',
'Controllers': '控件',
'Copyright': '版权所有',
'Create new application': '创建应用程序',
'Created By': 'Created By',
'Created On': 'Created On',
'Current request': '当前网络要求(request)',
'Current response': '当前网络响应(response)',
'Current session': '当前网络连接信息(session)',
'customize me!': '请调整我!',
'data uploaded': '数据已上传',
'Database': '数据库',
'Database %s select': '已选择 %s 数据库',
'Date and Time': '日期和时间',
'db': 'db',
'DB Model': '数据库模型',
'Delete': '删除',
'Delete:': '删除:',
'Demo': 'Demo',
'Deploy on Google App Engine': '发布到 Google App Engine',
'Deployment Recipes': 'Deployment Recipes',
'Description': '描述',
'DESIGN': '设计',
'design': '设计',
'Design for': '设计用于',
'DISK': 'DISK',
'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentation',
"Don't know what to do?": "Don't know what to do?",
'done!': '完成!',
'Download': '下载',
'E-mail': '电子邮件',
'EDIT': '编辑',
'Edit': '编辑',
'Edit application': '编辑应用程序',
'Edit current record': '编辑当前记录',
'edit profile': '编辑配置文件',
'Edit Profile': '编辑配置文件',
'Edit This App': '编辑本应用程序',
'Editing file': '编辑文件',
'Editing file "%s"': '编辑文件"%s"',
'Email and SMS': 'Email and SMS',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'Error logs for "%(app)s"': '"%(app)s"的错误记录',
'Errors': 'Errors',
'export as csv file': '以CSV格式导出',
'FAQ': 'FAQ',
'First name': '',
'Forgot username?': '忘记用户名?',
'Forms and Validators': 'Forms and Validators',
'Free Applications': 'Free Applications',
'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函数会显示 [passed].',
'Group ID': '群组编号',
'Groups': 'Groups',
'Hello World': 'Hello World',
'Home': 'Home',
'How did you get here?': 'How did you get here?',
'import': 'import',
'Import/Export': '导入/导出',
'Index': '索引',
'insert new': '插入新纪录',
'insert new %s': '插入新纪录 %s',
'Installed applications': '已安裝应用程序',
'Internal State': '內部状态',
'Introduction': 'Introduction',
'Invalid action': '非法操作(action)',
'Invalid email': '不符合电子邮件格式',
'Invalid Query': '无效的查询请求',
'invalid request': '网络要求无效',
'Is Active': 'Is Active',
'Key': 'Key',
'Language files (static strings) updated': '语言文件已更新',
'Languages': '各国语言',
'Last name': '',
'Last saved on:': '最后保存时间:',
'Layout': '网页布局',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts',
'License for': '软件授权',
'Live Chat': 'Live Chat',
'login': '登录',
'Login': '登录',
'Login to the Administrative Interface': '登录到管理员界面',
'logout': '登出',
'Logout': '登出',
'Lost Password': '忘记密码',
'Lost password?': '忘记密码?',
'Main Menu': '主菜单',
'Manage Cache': 'Manage Cache',
'Menu Model': '菜单模型(menu)',
'Models': '数据模型',
'Modified By': '修改者',
'Modified On': '修改时间',
'Modules': '程序模块',
'My Sites': 'My Sites',
'Name': '名字',
'New Record': '新记录',
'new record inserted': '已插入新记录',
'next 100 rows': '往后 100 笔',
'NO': '',
'No databases in this application': '该应用程序不含数据库',
'Object or table name': 'Object or table name',
'Online examples': '点击进入在线例子',
'or import from csv file': '或导入CSV文件',
'Origin': '原文',
'Original/Translation': '原文/翻译',
'Other Plugins': 'Other Plugins',
'Other Recipes': 'Other Recipes',
'Overview': '概览',
'Password': '密码',
"Password fields don't match": '密码不匹配',
'Peeking at file': '选择文件',
'Plugins': 'Plugins',
'Powered by': '基于下列技术构建:',
'Preface': 'Preface',
'previous 100 rows': '往前 100 笔',
'Python': 'Python',
'Query:': '查询:',
'Quick Examples': 'Quick Examples',
'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared',
'Recipes': 'Recipes',
'Record': '记录',
'record does not exist': '记录不存在',
'Record ID': '记录编号',
'Record id': '记录编号',
'Register': '注册',
'register': '注册',
'Registration identifier': 'Registration identifier',
'Registration key': '注册密钥',
'reload': 'reload',
'Remember me (for 30 days)': '记住我(30 天)',
'Reset Password key': '重置密码',
'Resolve Conflict file': '解决冲突文件',
'Role': '角色',
'Rows in Table': '在数据表里的记录',
'Rows selected': '笔记录被选择',
'Saved file hash:': '已保存文件的哈希值:',
'Semantic': 'Semantic',
'Services': 'Services',
'Size of cache:': 'Size of cache:',
'state': '状态',
'Static files': '静态文件',
'Statistics': '统计数据',
'Stylesheet': '网页样式表',
'submit': '提交',
'Submit': '提交',
'Support': 'Support',
'Sure you want to delete this object?': '确定要删除此对象?',
'Table': '数据表',
'Table name': '数据表名称',
'Testing application': '测试中的应用程序',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"query"应是类似 "db.table1.field1==\'value\'" 的条件表达式. "db.table1.field1==db.table2.field2"的形式则代表执行 JOIN SQL.',
'The Core': 'The Core',
'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s',
'The Views': '视图',
'There are no controllers': '沒有控件(controllers)',
'There are no models': '沒有数据库模型(models)',
'There are no modules': '沒有程序模块(modules)',
'There are no static files': '沒有静态文件',
'There are no translators, only default language is supported': '沒有对应的语言文件,仅支持原始语言',
'There are no views': '沒有视图',
'This App': '该应用',
'This is the %(filename)s template': '这是%(filename)s文件的模板(template)',
'Ticket': '问题清单',
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'Timestamp': '时间戳',
'Twitter': 'Twitter',
'Unable to check for upgrades': '查询新版本失败',
'Unable to download': '无法下载',
'Unable to download app': '无法下载应用程序',
'unable to parse csv file': '无法解析CSV文件',
'Update:': '更新:',
'Upload existing application': '上传已有应用程序',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式可得到更复杂的条件表达式, (...)&(...) 代表必须都满足, (...)|(...) 代表其一, ~(...)则代表否.',
'User %(id)s Logged-in': '用户 %(id)s 已登录',
'User %(id)s Registered': '用户 %(id)s 已注册',
'User ID': '用户编号',
'Verify Password': '验证密码',
'Videos': '视频',
'View': '查看',
'Views': '视图',
'Welcome': '欢迎',
'Welcome %s': '欢迎 %s',
'Welcome to web2py': '欢迎使用 web2py',
'Welcome to web2py!': '欢迎使用 web2py!',
'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s',
'YES': '',
'You are successfully running web2py': '您已成功运行 web2py',
'You can modify this application and adapt it to your needs': '请根据您的需要修改本程序',
'You visited the url %s': 'You visited the url %s',
}
+244
View File
@@ -0,0 +1,244 @@
# coding: utf8
{
'!langcode!': 'zh-cn',
'!langname!': '中文',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'\'". 但是 JOIN 的資料不可以使用 update 或是 delete"',
'%s %%{row} deleted': '已刪除 %s',
'%s %%{row} updated': '已更新 %s',
'%s selected': '%s 已選擇',
'%Y-%m-%d': '%Y-%m-%d',
'%Y-%m-%d %H:%M:%S': '%Y-%m-%d %H:%M:%S',
'(something like "it-it")': '(格式類似 "zh-tw")',
'A new version of web2py is available': '新版的 web2py 已發行',
'A new version of web2py is available: %s': '新版的 web2py 已發行: %s',
'about': '關於',
'About': '關於',
'About application': '關於本應用程式',
'Access Control': 'Access Control',
'Admin is disabled because insecure channel': '管理功能(Admin)在不安全連線環境下自動關閉',
'Admin is disabled because unsecure channel': '管理功能(Admin)在不安全連線環境下自動關閉',
'Administrative Interface': 'Administrative Interface',
'Administrative interface': '點此處進入管理介面',
'Administrator Password:': '管理員密碼:',
'Ajax Recipes': 'Ajax Recipes',
'An error occured, please %s the page': 'An error occured, please %s the page',
'appadmin is disabled because insecure channel': '因為來自非安全通道,管理介面關閉',
'Are you sure you want to delete file "%s"?': '確定要刪除檔案"%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"': '確定要移除應用程式 "%s"',
'Are you sure you want to uninstall application "%s"?': '確定要移除應用程式 "%s"',
'ATTENTION: Login requires a secure (HTTPS) connection or running on localhost.': '注意: 登入管理帳號需要安全連線(HTTPS)或是在本機連線(localhost).',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': '注意: 因為在測試模式不保證多執行緒安全性,也就是說不可以同時執行多個測試案例',
'ATTENTION: you cannot edit the running application!': '注意:不可編輯正在執行的應用程式!',
'Authentication': '驗證',
'Available Databases and Tables': '可提供的資料庫和資料表',
'Buy this book': 'Buy this book',
'cache': '快取記憶體',
'Cache': 'Cache',
'Cache Keys': 'Cache Keys',
'Cannot be empty': '不可空白',
'Cannot compile: there are errors in your app. Debug it, correct errors and try again.': '無法編譯:應用程式中含有錯誤,請除錯後再試一次.',
'Change Password': '變更密碼',
'change password': '變更密碼',
'Check to delete': '打勾代表刪除',
'Check to delete:': '點選以示刪除:',
'Clear CACHE?': 'Clear CACHE?',
'Clear DISK': 'Clear DISK',
'Clear RAM': 'Clear RAM',
'Client IP': '客戶端網址(IP)',
'Community': 'Community',
'Components and Plugins': 'Components and Plugins',
'Controller': '控件',
'Controllers': '控件',
'Copyright': '版權所有',
'Create new application': '創建應用程式',
'Created By': 'Created By',
'Created On': 'Created On',
'Current request': '目前網路資料要求(request)',
'Current response': '目前網路資料回應(response)',
'Current session': '目前網路連線資訊(session)',
'customize me!': '請調整我!',
'data uploaded': '資料已上傳',
'Database': '資料庫',
'Database %s select': '已選擇 %s 資料庫',
'Date and Time': '日期和時間',
'db': 'db',
'DB Model': '資料庫模組',
'Delete': '刪除',
'Delete:': '刪除:',
'Demo': 'Demo',
'Deploy on Google App Engine': '配置到 Google App Engine',
'Deployment Recipes': 'Deployment Recipes',
'Description': '描述',
'DESIGN': '設計',
'design': '設計',
'Design for': '設計為了',
'DISK': 'DISK',
'Disk Cache Keys': 'Disk Cache Keys',
'Disk Cleared': 'Disk Cleared',
'Documentation': 'Documentation',
"Don't know what to do?": "Don't know what to do?",
'done!': '完成!',
'Download': 'Download',
'E-mail': '電子郵件',
'EDIT': '編輯',
'Edit': '編輯',
'Edit application': '編輯應用程式',
'Edit current record': '編輯當前紀錄',
'edit profile': '編輯設定檔',
'Edit Profile': '編輯設定檔',
'Edit This App': '編輯本應用程式',
'Editing file': '編輯檔案',
'Editing file "%s"': '編輯檔案"%s"',
'Email and SMS': 'Email and SMS',
'enter an integer between %(min)g and %(max)g': 'enter an integer between %(min)g and %(max)g',
'Error logs for "%(app)s"': '"%(app)s"的錯誤紀錄',
'Errors': 'Errors',
'export as csv file': '以逗號分隔檔(csv)格式匯出',
'FAQ': 'FAQ',
'First name': '',
'Forgot username?': 'Forgot username?',
'Forms and Validators': 'Forms and Validators',
'Free Applications': 'Free Applications',
'Functions with no doctests will result in [passed] tests.': '沒有 doctests 的函式會顯示 [passed].',
'Group ID': '群組編號',
'Groups': 'Groups',
'Hello World': '嗨! 世界',
'Home': 'Home',
'How did you get here?': 'How did you get here?',
'import': 'import',
'Import/Export': '匯入/匯出',
'Index': '索引',
'insert new': '插入新資料',
'insert new %s': '插入新資料 %s',
'Installed applications': '已安裝應用程式',
'Internal State': '內部狀態',
'Introduction': 'Introduction',
'Invalid action': '不合法的動作(action)',
'Invalid email': '不合法的電子郵件',
'Invalid Query': '不合法的查詢',
'invalid request': '不合法的網路要求(request)',
'Is Active': 'Is Active',
'Key': 'Key',
'Language files (static strings) updated': '語言檔已更新',
'Languages': '各國語言',
'Last name': '',
'Last saved on:': '最後儲存時間:',
'Layout': '網頁配置',
'Layout Plugins': 'Layout Plugins',
'Layouts': 'Layouts',
'License for': '軟體版權為',
'Live Chat': 'Live Chat',
'login': '登入',
'Login': '登入',
'Login to the Administrative Interface': '登入到管理員介面',
'logout': '登出',
'Logout': '登出',
'Lost Password': '密碼遺忘',
'Lost password?': 'Lost password?',
'Main Menu': '主選單',
'Manage Cache': 'Manage Cache',
'Menu Model': '選單模組(menu)',
'Models': '資料模組',
'Modified By': 'Modified By',
'Modified On': 'Modified On',
'Modules': '程式模組',
'My Sites': 'My Sites',
'Name': '名字',
'New Record': '新紀錄',
'new record inserted': '已插入新紀錄',
'next 100 rows': '往後 100 筆',
'NO': '',
'No databases in this application': '這應用程式不含資料庫',
'Object or table name': 'Object or table name',
'Online examples': '點此處進入線上範例',
'or import from csv file': '或是從逗號分隔檔(CSV)匯入',
'Origin': '原文',
'Original/Translation': '原文/翻譯',
'Other Plugins': 'Other Plugins',
'Other Recipes': 'Other Recipes',
'Overview': 'Overview',
'Password': '密碼',
"Password fields don't match": '密碼欄不匹配',
'Peeking at file': '選擇檔案',
'Plugins': 'Plugins',
'Powered by': '基於以下技術構建:',
'Preface': 'Preface',
'previous 100 rows': '往前 100 筆',
'Python': 'Python',
'Query:': '查詢:',
'Quick Examples': 'Quick Examples',
'RAM': 'RAM',
'RAM Cache Keys': 'RAM Cache Keys',
'Ram Cleared': 'Ram Cleared',
'Recipes': 'Recipes',
'Record': '紀錄',
'record does not exist': '紀錄不存在',
'Record ID': '紀錄編號',
'Record id': '紀錄編號',
'Register': '註冊',
'register': '註冊',
'Registration identifier': 'Registration identifier',
'Registration key': '註冊金鑰',
'reload': 'reload',
'Remember me (for 30 days)': '記住我(30 天)',
'Reset Password key': '重設密碼',
'Resolve Conflict file': '解決衝突檔案',
'Role': '角色',
'Rows in Table': '在資料表裏的資料',
'Rows selected': '筆資料被選擇',
'Saved file hash:': '檔案雜湊值已紀錄:',
'Semantic': 'Semantic',
'Services': 'Services',
'Size of cache:': 'Size of cache:',
'state': '狀態',
'Static files': '靜態檔案',
'Statistics': 'Statistics',
'Stylesheet': '網頁風格檔',
'submit': 'submit',
'Submit': '傳送',
'Support': 'Support',
'Sure you want to delete this object?': '確定要刪除此物件?',
'Table': '資料表',
'Table name': '資料表名稱',
'Testing application': '測試中的應用程式',
'The "query" is a condition like "db.table1.field1==\'value\'". Something like "db.table1.field1==db.table2.field2" results in a SQL JOIN.': '"查詢"是一個像 "db.表1.欄位1==\'\'" 的條件式. 以"db.表1.欄位1==db.表2.欄位2"方式則相當於執行 JOIN SQL.',
'The Core': 'The Core',
'The output of the file is a dictionary that was rendered by the view %s': 'The output of the file is a dictionary that was rendered by the view %s',
'The Views': 'The Views',
'There are no controllers': '沒有控件(controllers)',
'There are no models': '沒有資料庫模組(models)',
'There are no modules': '沒有程式模組(modules)',
'There are no static files': '沒有靜態檔案',
'There are no translators, only default language is supported': '沒有翻譯檔,只支援原始語言',
'There are no views': '沒有視圖',
'This App': 'This App',
'This is the %(filename)s template': '這是%(filename)s檔案的樣板(template)',
'Ticket': '問題單',
'Time in Cache (h:m:s)': 'Time in Cache (h:m:s)',
'Timestamp': '時間標記',
'Twitter': 'Twitter',
'Unable to check for upgrades': '無法做升級檢查',
'Unable to download': '無法下載',
'Unable to download app': '無法下載應用程式',
'unable to parse csv file': '無法解析逗號分隔檔(csv)',
'Update:': '更新:',
'Upload existing application': '更新存在的應用程式',
'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...) for NOT to build more complex queries.': '使用下列方式來組合更複雜的條件式, (...)&(...) 代表同時存在的條件, (...)|(...) 代表擇一的條件, ~(...)則代表反向條件.',
'User %(id)s Logged-in': '使用者 %(id)s 已登入',
'User %(id)s Registered': '使用者 %(id)s 已註冊',
'User ID': '使用者編號',
'Verify Password': '驗證密碼',
'Videos': 'Videos',
'View': '視圖',
'Views': '視圖',
'Welcome': 'Welcome',
'Welcome %s': '歡迎 %s',
'Welcome to web2py': '歡迎使用 web2py',
'Welcome to web2py!': 'Welcome to web2py!',
'Which called the function %s located in the file %s': 'Which called the function %s located in the file %s',
'YES': '',
'You are successfully running web2py': 'You are successfully running web2py',
'You can modify this application and adapt it to your needs': 'You can modify this application and adapt it to your needs',
'You visited the url %s': 'You visited the url %s',
}
+1 -1
View File
@@ -1,6 +1,6 @@
# coding: utf8
{
'!langcode!': 'zh-cn',
'!langcode!': 'zh-tw',
'!langname!': '中文',
'"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"更新" 是選擇性的條件式, 格式就像 "欄位1=\'\'". 但是 JOIN 的資料不可以使用 update 或是 delete"',
'%s %%{row} deleted': '已刪除 %s',
+2
View File
@@ -136,3 +136,5 @@ def _():
]
)]
if DEVELOPMENT_MENU: _()
if "auth" in locals(): auth.wikimenu()
+2 -1
View File
@@ -92,7 +92,7 @@ div.flash {
position:fixed;
padding:10px;
top:48px;
right:50px;
right:250px;
min-width:280px;
opacity:0.95;
margin:0px 0px 10px 10px;
@@ -191,6 +191,7 @@ div.error {
.web2py_grid {width:100%}
.web2py_grid table {width:100%}
.web2py_grid tbody td {padding:2px 5px 2px 5px; vertical-align: middle;}
.web2py_grid .web2py_form td {vertical-align: top;}
.web2py_grid thead th,.web2py_grid tfoot td {
background-color:#EAEAEA;
@@ -8,21 +8,28 @@ jQuery(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(){
var wid = document.documentElement.clientWidth; //faster than $(window).width() and cross browser
if (wid>=980){
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
mi = jQuery(this);
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeOut(function(){mi.removeClass('open')});
});
};
jQuery('ul.nav a.dropdown-toggle').parent().hover(function(){
adjust_height_of_collapsed_nav();
mi = jQuery(this).addClass('open');
mi.children('.dropdown-menu').stop(true, true).delay(200).fadeIn(400);
}, function(){
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');});
// make all buttons bootstrap buttons
jQuery('button, form input[type="submit"], form input[type="button"]').addClass('btn');
});
});
+2 -2
View File
@@ -91,8 +91,8 @@
<div class="container">
<!-- Masthead ================================================== -->
{{if response.title:}}
<header class="mastheader row" id="header">
{{if response.title:}}
<div class="span12">
<div class="page-header">
<h1>
@@ -101,8 +101,8 @@
</h1>
</div>
</div>
{{pass}}
</header>
{{pass}}
<section id="main" class="main row">
{{if left_sidebar_enabled:}}
+1 -1
View File
@@ -3,7 +3,7 @@
var w2p_ajax_confirm_message = "{{=T('Are you sure you want to delete this object?')}}";
var w2p_ajax_date_format = "{{=T('%Y-%m-%d')}}";
var w2p_ajax_datetime_format = "{{=T('%Y-%m-%d %H:%M:%S')}}";
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.vars))) }}'
var ajax_error_500 = '{{=XML(T('An error occured, please %s the page') % A(T('reload'), _href=URL(args=request.args, vars=request.get_vars))) }}'
//--></script>
{{
response.files.insert(0,URL('static','js/jquery.js'))
+82 -50
View File
@@ -437,7 +437,7 @@ class Cache(object):
# been accounted for
logger.warning('no cache.disk (AttributeError)')
def client(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
def action(self, time_expire=DEFAULT_TIME_EXPIRE, cache_model=None,
prefix=None, session=False, vars=True, lang=True,
user_agent=False, public=True, valid_statuses=None,
quick=None):
@@ -461,60 +461,92 @@ class Cache(object):
fast overrides with initial strings, e.g. 'SVLP' or 'VLP', or 'VLP'
"""
from gluon import current
from gluon.http import HTTP
def wrap(func):
def wrapped_f():
if current.request.env.request_method == 'GET':
if 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_:
cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
vary = None
else:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model:
cache_key = [current.request.env.path_info, current.response.view]
if session_:
cache_key.append(current.response.session_id)
elif user_agent_:
if user_agent_ is True:
cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent())
else:
cache_key.append(str(user_agent_.items()))
if vars_:
cache_key.append(current.request.env.query_string)
if lang_:
cache_key.append(current.T.accepted_language)
cache_key = hashlib.md5('__'.join(cache_key)).hexdigest()
if prefix:
cache_key = prefix + cache_key
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
if current.request.env.request_method != 'GET':
return func()
if 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_:
cache_control += ', public'
expires = (current.request.utcnow + datetime.timedelta(seconds=time_expire)).strftime('%a, %d %b %Y %H:%M:%S GMT')
vary = None
else:
cache_control += ', private'
expires = 'Fri, 01 Jan 1990 00:00:00 GMT'
if cache_model:
#figure out the correct cache key
cache_key = [current.request.env.path_info, current.response.view]
if session_:
cache_key.append(current.response.session_id)
elif user_agent_:
if user_agent_ is True:
cache_key.append("%(is_mobile)s_%(is_tablet)s" % current.request.user_agent())
else:
cache_key.append(str(user_agent_.items()))
if vars_:
cache_key.append(current.request.env.query_string)
if lang_:
cache_key.append(current.T.accepted_language)
cache_key = hashlib.md5('__'.join(cache_key)).hexdigest()
if prefix:
cache_key = prefix + cache_key
try:
#action returns something
rtn = cache_model(cache_key, lambda : func(), time_expire=time_expire)
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
rtn = cache_model(cache_key, lambda : e.body, time_expire=time_expire)
http, status = HTTP(e.status, rtn, **e.headers), e.status
else:
#action raised a generic exception
http = None
else:
#no server-cache side involved
try:
#action returns something
rtn = func()
send_headers = False
if isinstance(valid_statuses, list):
if current.response.status in valid_statuses:
send_headers = True
elif valid_statuses is None:
if str(current.response.status)[0] in '123':
send_headers = True
http, status = None, current.response.status
except HTTP, e:
#action raises HTTP (can still be valid)
status = e.status
http = HTTP(e.status, e.body, **e.headers)
else:
#action raised a generic exception
http = None
send_headers = False
if http and isinstance(valid_statuses, list):
if status in valid_statuses:
send_headers = True
elif valid_statuses is None:
if str(status)[0] in '123':
send_headers = True
if send_headers:
headers = {
'Pragma' : None,
'Expires' : expires,
'Cache-Control' : cache_control
}
current.response.headers.update(headers)
if cache_model and not send_headers:
#we cached already the value, but the status is not valid
#so we need to delete the cached value
cache_model(cache_key, None)
if http:
if send_headers:
current.response.headers['Pragma'] = None
current.response.headers['Expires'] = expires
current.response.headers['Cache-Control'] = cache_control
if cache_model and not send_headers:
cache_model(cache_key, None)
return rtn
return func()
http.headers.update(current.response.headers)
raise http
return rtn
wrapped_f.__name__ = func.__name__
wrapped_f.__doc__ = func.__doc__
return wrapped_f
+4 -4
View File
@@ -443,10 +443,10 @@ CONTENT_TYPE = {
'.odp': 'application/vnd.oasis.opendocument.presentation',
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
'.odt': 'application/vnd.oasis.opendocument.text',
'.oga': 'audio/x-speex+ogg',
'.ogg': 'video/x-theora+ogg',
'.oga': 'audio/ogg',
'.ogg': 'application/ogg',
'.ogm': 'video/x-ogm+ogg',
'.ogv': 'video/x-theora+ogg',
'.ogv': 'video/ogg',
'.ogx': 'application/ogg',
'.old': 'application/x-trash',
'.oleo': 'application/x-oleo',
@@ -518,7 +518,7 @@ CONTENT_TYPE = {
'.pln': 'application/x-planperfect',
'.pls': 'audio/x-scpls',
'.pm': 'application/x-perl',
'.png': 'image/x-apple-ios-png',
'.png': 'image/png',
'.pnm': 'image/x-portable-anymap',
'.pntg': 'image/x-macpaint',
'.po': 'text/x-gettext-translation',
+1 -1
View File
@@ -815,7 +815,7 @@ class Connection(object):
outrec = Record(FCGI_UNKNOWN_TYPE)
outrec.contentData = struct.pack(FCGI_UnknownTypeBody, inrec.type)
outrec.contentLength = FCGI_UnknownTypeBody_LEN
self.writeRecord(rec)
self.writeRecord(outrec)
class MultiplexedConnection(Connection):
"""
+1 -1
View File
@@ -51,7 +51,7 @@ def THUMB(image, nx=120, ny=120, gae=False, name='thumb'):
request = current.request
from PIL import Image
import os
img = Image.open(request.folder + 'uploads/' + image)
img = Image.open(os.path.join(request.folder,'uploads',image))
img.thumbnail((nx, ny), Image.ANTIALIAS)
root, ext = os.path.splitext(image)
thumb = '%s_%s%s' % (root, name, ext)
@@ -94,9 +94,8 @@ class DropboxAccount(object):
return form
def logout_url(self, next="/"):
current.session.dropbox_request_token = None
self.sess.unlink()
current.session.auth = None
redirect('https://www.dropbox.com/logout')
return next
def get_client(self):
@@ -106,15 +105,15 @@ class DropboxAccount(object):
def put(self, filename, file):
if not hasattr(self,'client'): self.get_client()
return json.loads(self.client.put_file(filename, file))['bytes']
return self.client.put_file(filename, file)['bytes']
def get(self, filename, file):
def get(self, filename):
if not hasattr(self,'client'): self.get_client()
return self.client.get_file(filename)
def dir(self, path):
if not hasattr(self,'client'): self.get_client()
return json.loads(self.client.metadata(path))
return self.client.metadata(path)
def use_dropbox(auth, filename='private/dropbox.key', **kwargs):
@@ -0,0 +1,97 @@
#!/usr/bin/env python
# coding: utf8
"""
LoginRadius Authentication for web2py
Developed by Nathan Freeze (Copyright © 2013)
Email <nathan@freezable.com>
This file contains code to allow using loginradius.com
authentication services with web2py
"""
import os
from gluon import *
from gluon.storage import Storage
from gluon.contrib.simplejson import JSONDecodeError
from gluon.tools import fetch
import gluon.contrib.simplejson as json
class LoginRadiusAccount(object):
"""
from gluon.contrib.login_methods.loginradius_account import LoginRadiusAccount
auth.settings.actions_disabled=['register','change_password',
'request_reset_password']
auth.settings.login_form = LoginRadiusAccount(request,
api_key="...",
api_secret="...",
url = "http://localhost:8000/%s/default/user/login" % request.application)
"""
def __init__(self, request, api_key="", api_secret="",
url=None, on_login_failure=None):
self.request = request
self.api_key = api_key
self.api_secret = api_secret
self.url = url
self.auth_base_url = "https://hub.loginradius.com/UserProfile.ashx/"
self.profile = None
self.on_login_failure = on_login_failure
self.mappings = Storage()
def defaultmapping(profile):
first_name = profile.get('FirstName')
last_name = profile.get('LastName')
email = profile.get('Email', [{}])[0].get('Value')
reg_id = profile.get('ID', '')
username = profile.get('ProfileName', email)
return dict(registration_id=reg_id, username=username, email=email,
first_name=first_name, last_name=last_name)
self.mappings.default = defaultmapping
def get_user(self):
request = self.request
user = None
if request.vars.token:
try:
auth_url = self.auth_base_url + self.api_secret + "/" + request.vars.token
json_data = fetch(auth_url, headers={'User-Agent': "LoginRadius - Python - SDK"})
self.profile = json.loads(json_data)
provider = self.profile['Provider']
mapping = self.mappings.get(provider, self.mappings['default'])
user = mapping(self.profile)
except (JSONDecodeError, KeyError):
pass
if user is None and self.on_login_failure:
redirect(self.on_login_failure)
return user
def login_form(self):
loginradius_url = "https://hub.loginradius.com/include/js/LoginRadius.js"
loginradius_lib = SCRIPT(_src=loginradius_url, _type='text/javascript')
container = DIV(_id="interfacecontainerdiv", _class='interfacecontainerdiv')
widget = SCRIPT("""var options={}; options.login=true;
LoginRadius_SocialLogin.util.ready(function () {
$ui = LoginRadius_SocialLogin.lr_login_settings;
$ui.interfacesize = "";$ui.apikey = "%s";
$ui.callback=""; $ui.lrinterfacecontainer ="interfacecontainerdiv";
LoginRadius_SocialLogin.init(options); });""" % self.api_key)
form = DIV(container, loginradius_lib, widget)
return form
def use_loginradius(auth, filename='private/loginradius.key', **kwargs):
path = os.path.join(current.request.folder, filename)
if os.path.exists(path):
request = current.request
domain, public_key, private_key = open(path, 'r').read().strip().split(':')
url = URL('default', 'user', args='login', scheme=True)
auth.settings.actions_disabled = \
['register', 'change_password', 'request_reset_password']
auth.settings.login_form = LoginRadiusAccount(
request, api_key=public_key, api_secret=private_key,
url=url, **kwargs)
@@ -176,7 +176,7 @@ class OAuthAccount(object):
HTTP = self.globals['HTTP']
raise HTTP(307,
raise HTTP(302,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url)
@@ -285,7 +285,7 @@ server for requests. It can be used for the optional"scope" parameters for Face
if self.args:
data.update(self.args)
auth_request_url = self.auth_url + "?" + urlencode(data)
raise HTTP(307,
raise HTTP(302,
"You are not authenticated: you are being redirected to the <a href='" + auth_request_url + "'> authentication server</a>",
Location=auth_request_url)
return
+14 -2
View File
@@ -544,10 +544,10 @@ regex_list=re.compile('^(?:(?:(#{1,6})|(?:(\.+|\++|\-+)(\.)?))\s*)?(.*)$')
regex_bq_headline=re.compile('^(?:(\.+|\++|\-+)(\.)?\s+)?(-{3}-*)$')
regex_tq=re.compile('^(-{3}-*)(?::(?P<c>[a-zA-Z][_a-zA-Z\-\d]*)(?:\[(?P<p>[a-zA-Z][_a-zA-Z\-\d]*)\])?)?$')
regex_proto = re.compile(r'(?<!["\w>/=])(?P<p>\w+):(?P<k>\w+://[\w\d\-+=?%&/:.]+)', re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.]+)',re.M)
regex_auto = re.compile(r'(?<!["\w>/=])(?P<k>\w+://[\w\d\-+_=?%&/:.,;#]+\w)',re.M)
regex_link=re.compile(r'('+LINK+r')|\[\[(?P<s>.+?)\]\]',re.S)
regex_link_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?(?:\s+(?P<p>popup))?\s*$',re.S)
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio)(?:\s+(?P<w>\d+px))?\s*$',re.S)
regex_media_level2=re.compile(r'^(?P<t>\S.*?)?(?:\s+\[(?P<a>.+?)\])?(?:\s+(?P<k>\S+))?\s+(?P<p>img|IMG|left|right|center|video|audio|blockleft|blockright)(?:\s+(?P<w>\d+px))?\s*$',re.S)
regex_markmin_escape = re.compile(r"(\\*)(['`:*~\\[\]{}@\$+\-.#\n])")
regex_backslash = re.compile(r"\\(['`:*~\\[\]{}@\$+\-.#\n])")
@@ -859,6 +859,8 @@ def render(text,
if autolinks=="default": autolinks = autolinks_simple
if protolinks=="default": protolinks = protolinks_simple
pp='\n' if pretty_print else ''
if isinstance(text,unicode):
text = text.encode('utf8')
text = str(text or '')
text = regex_backslash.sub(lambda m: m.group(1).translate(ttab_in), text)
text = text.replace('\x05','') # concatenate strings separeted by \\n
@@ -1242,8 +1244,18 @@ def render(text,
if p == 'center':
p_begin = '<p style="text-align:center">'
p_end = '</p>'+pp
elif p == 'blockleft':
p_begin = '<p style="text-align:left">'
p_end = '</p>'+pp
elif p == 'blockright':
p_begin = '<p style="text-align:right">'
p_end = '</p>'+pp
elif p in ('left','right'):
style = ('float:%s' % p)+(';%s' % style if style else '')
if t and regex_auto.match(t):
p_begin = p_begin + '<a href="%s">' % t
p_end = '</a>' + p_end
t = ''
if style:
style = ' style="%s"' % style
if p in ('video','audio'):
+1 -1
View File
@@ -80,7 +80,7 @@ class MemcacheClientObj(Client):
newKey = self.__keyFormat__(key)
obj = Client.get(self, newKey)
if obj:
if isinstance(obj,(int,double,long)):
if isinstance(obj,(int,float,long)):
return Client.incr(self, newKey, value)
else:
value += obj[1]
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-
# Plural-Forms for id (Indonesian)
nplurals=2 # Indonesian language has 2 forms:
# 1 singular and 1 plural
# Determine plural_id for number *n* as sequence of positive
# integers: 0,1,...
# NOTE! For singular form ALWAYS return plural_id = 0
get_plural_id = lambda n: int(n != 1)
# Construct and return plural form of *word* using
# *plural_id* (which ALWAYS>0). This function will be executed
# for words (or phrases) not found in plural_dict dictionary
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env python
# -*- coding: utf8 -*-
# Plural-Forms for id (Malay)
nplurals=2 # Malay language has 2 forms:
# 1 singular and 1 plural
# Determine plural_id for number *n* as sequence of positive
# integers: 0,1,...
# NOTE! For singular form ALWAYS return plural_id = 0
get_plural_id = lambda n: int(n != 1)
# Construct and return plural form of *word* using
# *plural_id* (which ALWAYS>0). This function will be executed
# for words (or phrases) not found in plural_dict dictionary
# construct_plural_form = lambda word, plural_id: (word + 'suffix')
File diff suppressed because one or more lines are too long
+137 -43
View File
@@ -1,8 +1,7 @@
"""
Developed by 616d41631bff906704951934ffe4015e
Developed by niphlod@gmail.com
Released under web2py license because includes gluon/cache.py source code
"""
import redis
from redis.exceptions import ConnectionError
from gluon import current
@@ -12,6 +11,8 @@ import time
import re
import logging
import thread
import random
logger = logging.getLogger("web2py.cache.redis")
@@ -23,15 +24,42 @@ def RedisCache(*args, **vars):
Usage example: put in models
from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)
cache.redis = RedisCache('localhost:6379',db=None, debug=True, with_lock=True)
:param db: redis db to use (0..16)
:param debug: if True adds to stats() the total_hits and misses
:param with_lock: sets the default locking mode for creating new keys.
By default is False (usualy when you choose Redis you do it
for performances reason)
When True, only one thread/process can set a value concurrently
When you use cache.redis directly you can use
value = cache.redis('mykey', lambda: time.time(), with_lock=True)
to enforce locking. The with_lock parameter overrides the one set in the
cache.redis instance creation
cache.redis.stats()
returns a dictionary with statistics of Redis server
with one additional key ('w2p_keys') showing all keys currently set
from web2py with their TTL
return a dictionary with statistics of Redis server
with one additional key ('w2p_keys') showing all keys currently set
from web2py with their TTL
if debug=True additional tracking is activate and another key is added
('w2p_stats') showing total_hits and misses
A little wording on how keys are stored (and why the cache_it() function
and the clear() one look a little bit convoluted): there are a lot of
libraries that just store values and then use the KEYS command to delete it.
Until recent releases of this module, that technique was used here too.
In the need of deleting specific keys in a database with zillions keys in it
(other web2py apps, other applications in the need of a Redis stack) the
KEYS command is slow (it needs to scan every key in the database).
So, we use Redis 'sets' to store keys in "buckets"...
- every key created gets "indexed" in a bucket
- all buckets are indexed in a fixed key that never expires
- all keys generated within the same minute go in the same bucket
- every bucket is then set to expire when every key within it is expired
When we need to clear() cached keys:
- we tell Redis to SUNION all buckets
- gives us just the keys that are not expired yet
- buckets that are expired are removed from the fixed set
- we scan the keys and then delete them
"""
locker.acquire()
@@ -49,13 +77,15 @@ class RedisClient(object):
MAX_RETRIES = 5
RETRIES = 0
def __init__(self, server='localhost:6379', db=None, debug=False):
def __init__(self, server='localhost:6379', db=None, debug=False, with_lock=False):
self.server = server
self.db = db or 0
host, port = (self.server.split(':') + ['6379'])[:2]
port = int(port)
self.request = current.request
self.debug = debug
self.with_lock = with_lock
self.prefix = "w2p:%s:" % (self.request.application)
if self.request:
app = self.request.application
else:
@@ -70,67 +100,112 @@ class RedisClient(object):
else:
self.storage = self.meta_storage[app]
self.cache_set_key = 'w2p:%s:___cache_set' % (self.request.application)
self.r_server = redis.Redis(host=host, port=port, db=self.db)
def __call__(self, key, f, time_expire=300):
def __call__(self, key, f, time_expire=300, with_lock=None):
if with_lock is None:
with_lock = self.with_lock
if time_expire is None:
time_expire = 24 * 60 * 60
newKey = self.__keyFormat__(key)
value = None
ttl = 0
try:
if time_expire is None:
time_expire = 24 * 60 * 60
newKey = self.__keyFormat__(key)
value = None
#is there a value
obj = self.r_server.get(newKey)
ttl = self.r_server.ttl(newKey) or 0
#what's its ttl
if obj:
ttl = self.r_server.ttl(newKey)
if ttl > time_expire:
obj = None
if obj:
#was cached
if self.debug:
self.r_server.incr('web2py_cache_statistics:hit_total')
value = pickle.loads(obj)
elif f is None:
#delete and never look back
self.r_server.delete(newKey)
else:
if self.debug:
self.r_server.incr('web2py_cache_statistics:misses')
value = f()
if time_expire == 0:
time_expire = 1
self.r_server.setex(newKey, pickle.dumps(value), time_expire)
#naive distributed locking
if with_lock:
lock_key = '%s:__lock' % newKey
try:
while True:
lock = self.r_server.setnx(lock_key, 1)
if lock:
value = self.cache_it(newKey, f, time_expire)
break
else:
time.sleep(0.2)
#did someone else create it in the meanwhile ?
obj = self.r_server.get(newKey)
if obj:
value = pickle.loads(obj)
break
finally:
self.r_server.delete(lock_key)
else:
#without distributed locking
value = self.cache_it(newKey, f, time_expire)
return value
except ConnectionError:
return self.retry_call(key, f, time_expire)
return self.retry_call(key, f, time_expire, with_lock)
def retry_call(self, key, f, time_expire):
def cache_it(self, key, f, time_expire):
if self.debug:
self.r_server.incr('web2py_cache_statistics:misses')
cache_set_key = self.cache_set_key
expireat = int(time.time() + time_expire) + 120
bucket_key = "%s:%s" % (cache_set_key, expireat / 60)
value = f()
value_ = pickle.dumps(value)
if time_expire == 0:
time_expire = 1
self.r_server.setex(key, value_, time_expire)
#print '%s will expire on %s: it goes in bucket %s' % (key, time.ctime(expireat))
#print 'that will expire on %s' % (bucket_key, time.ctime(((expireat/60) + 1)*60))
p = self.r_server.pipeline()
#add bucket to the fixed set
p.sadd(cache_set_key, bucket_key)
#sets the key
p.setex(key, value_, time_expire)
#add the key to the bucket
p.sadd(bucket_key, key)
#expire the bucket properly
p.expireat(bucket_key, ((expireat/60) + 1)*60)
p.execute()
return value
def retry_call(self, key, f, time_expire, with_locking):
self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping %s seconds before reconnecting" %
(2 * self.RETRIES))
time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug)
return self.__call__(key, f, time_expire)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.__call__(key, f, time_expire, with_locking)
else:
self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % (
self.server))
def increment(self, key, value=1, time_expire=300):
def increment(self, key, value=1):
try:
newKey = self.__keyFormat__(key)
obj = self.r_server.get(newKey)
if obj:
return self.r_server.incr(newKey, value)
else:
self.r_server.setex(newKey, value, time_expire)
return value
return self.r_server.incr(newKey, value)
except ConnectionError:
return self.retry_increment(key, value, time_expire)
return self.retry_increment(key, value)
def retry_increment(self, key, value, time_expire):
def retry_increment(self, key, value):
self.RETRIES += 1
if self.RETRIES <= self.MAX_RETRIES:
logger.error("sleeping some seconds before reconnecting")
time.sleep(2 * self.RETRIES)
self.__init__(self.server, self.db, self.debug)
return self.increment(key, value, time_expire)
self.__init__(self.server, self.db, self.debug, self.with_lock)
return self.increment(key, value)
else:
self.RETRIES = 0
raise ConnectionError('Redis instance is unavailable at %s' % (
@@ -142,14 +217,34 @@ class RedisClient(object):
clear cache entries
"""
r = re.compile(regex)
prefix = "w2p:%s:" % (self.request.application)
#get all buckets
buckets = self.r_server.smembers(self.cache_set_key)
#get all keys in buckets
if buckets:
keys = self.r_server.sunion(buckets)
else:
return
prefix = self.prefix
pipe = self.r_server.pipeline()
for a in self.r_server.keys("%s*" %
(prefix)):
for a in keys:
if r.match(str(a).replace(prefix, '', 1)):
pipe.delete(a)
if random.randrange(0,100) < 10:
#do this just once in a while (10% chance)
self.clear_buckets(buckets)
pipe.execute()
def clear_buckets(self, buckets):
p = self.r_server.pipeline()
for b in buckets:
if not self.r_server.exists(b):
p.srem(self.cache_set_key, b)
p.execute()
def delete(self, key):
newKey = self.__keyFormat__(key)
return self.r_server.delete(newKey)
def stats(self):
statscollector = self.r_server.info()
if self.debug:
@@ -159,12 +254,11 @@ class RedisClient(object):
misses=self.r_server.get('web2py_cache_statistics:misses')
)
statscollector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)):
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = \
self.r_server.ttl(a)
statscollector['w2p_keys']["%s_expire_in_sec" % (a)] = self.r_server.ttl(a)
return statscollector
def __keyFormat__(self, key):
return 'w2p:%s:%s' % (self.request.application,
key.replace(' ', '_'))
return '%s%s' % (self.prefix, key.replace(' ', '_'))
+73 -33
View File
@@ -29,11 +29,12 @@ def RedisSession(*args, **vars):
locker.acquire()
try:
if not hasattr(RedisSession, 'redis_instance'):
RedisSession.redis_instance = RedisClient(*args, **vars)
instance_name = 'redis_instance_' + current.request.application
if not hasattr(RedisSession, instance_name):
setattr(RedisSession, instance_name, RedisClient(*args, **vars))
return getattr(RedisSession, instance_name)
finally:
locker.release()
return RedisSession.redis_instance
class RedisClient(object):
@@ -41,8 +42,10 @@ class RedisClient(object):
meta_storage = {}
MAX_RETRIES = 5
RETRIES = 0
_release_script = None
def __init__(self, server='localhost:6379', db=None, debug=False, session_expiry=False):
def __init__(self, server='localhost:6379', db=None, debug=False,
session_expiry=False, with_lock=False):
"""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
@@ -57,8 +60,12 @@ class RedisClient(object):
else:
self.app = ''
self.r_server = redis.Redis(host=host, port=port, db=self.db)
if with_lock:
RedisClient._release_script = \
self.r_server.register_script(_LUA_RELEASE_LOCK)
self.tablename = None
self.session_expiry = session_expiry
self.with_lock = with_lock
def get(self, what, default):
return self.tablename
@@ -70,7 +77,8 @@ class RedisClient(object):
def define_table(self, tablename, *fields, **args):
if not self.tablename:
self.tablename = MockTable(
self, self.r_server, tablename, self.session_expiry)
self, self.r_server, tablename, self.session_expiry,
self.with_lock)
return self.tablename
def __getitem__(self, key):
@@ -87,7 +95,7 @@ class RedisClient(object):
class MockTable(object):
def __init__(self, db, r_server, tablename, session_expiry):
def __init__(self, db, r_server, tablename, session_expiry, with_lock=False):
self.db = db
self.r_server = r_server
self.tablename = tablename
@@ -100,15 +108,14 @@ class MockTable(object):
self.id_idx = "%s:id_idx" % self.keyprefix
#remember the session_expiry setting
self.session_expiry = session_expiry
def getserial(self):
#return an auto-increment id
return "%s" % self.r_server.incr(self.serial, 1)
self.with_lock = with_lock
def __getattr__(self, key):
if key == 'id':
#return a fake query. We need to query it just by id for normal operations
self.query = MockQuery(field='id', db=self.r_server, prefix=self.keyprefix, session_expiry=self.session_expiry)
self.query = MockQuery(field='id', db=self.r_server,
prefix=self.keyprefix, session_expiry=self.session_expiry,
with_lock=self.with_lock)
return self.query
elif key == '_db':
#needed because of the calls in sessions2trash.py and globals.py
@@ -119,14 +126,21 @@ class MockTable(object):
#'locked', 'client_ip','created_datetime','modified_datetime'
#'unique_key', 'session_data'
#retrieve a new key
newid = self.getserial()
key = "%s:%s" % (self.keyprefix, newid)
#add it to the index
self.r_server.sadd(self.id_idx, key)
#set a hash key with the Storage
self.r_server.hmset(key, kwargs)
if self.session_expiry:
self.r_server.expire(key, self.session_expiry)
newid = str(self.r_server.incr(self.serial))
key = self.keyprefix + ':' + newid
if self.with_lock:
key_lock = key + ':lock'
acquire_lock(self.r_server, key_lock, newid)
with self.r_server.pipeline() as pipe:
#add it to the index
pipe.sadd(self.id_idx, key)
#set a hash key with the Storage
pipe.hmset(key, kwargs)
if self.session_expiry:
pipe.expire(key, self.session_expiry)
pipe.execute()
if self.with_lock:
release_lock(self.r_server, key_lock, newid)
return newid
@@ -134,13 +148,15 @@ class MockQuery(object):
"""a fake Query object that supports querying by id
and listing all keys. No other operation is supported
"""
def __init__(self, field=None, db=None, prefix=None, session_expiry=False):
def __init__(self, field=None, db=None, prefix=None, session_expiry=False,
with_lock=False):
self.field = field
self.value = None
self.db = db
self.keyprefix = prefix
self.op = None
self.session_expiry = session_expiry
self.with_lock = with_lock
def __eq__(self, value, op='eq'):
self.value = value
@@ -153,12 +169,11 @@ class MockQuery(object):
def select(self):
if self.op == 'eq' and self.field == 'id' and self.value:
#means that someone wants to retrieve the key self.value
rtn = self.db.hgetall("%s:%s" % (self.keyprefix, self.value))
if rtn == dict():
#return an empty resultset for non existing key
return []
else:
return [Storage(rtn)]
key = self.keyprefix + ':' + self.value
if self.with_lock:
acquire_lock(self.db, key + ':lock', self.value)
rtn = self.db.hgetall(key)
return [Storage(rtn)] if rtn else []
elif self.op == 'ge' and self.field == 'id' and self.value == 0:
#means that someone wants the complete list
rtn = []
@@ -167,13 +182,11 @@ class MockQuery(object):
allkeys = self.db.smembers(id_idx)
for sess in allkeys:
val = self.db.hgetall(sess)
if val == dict():
if not val:
if self.session_expiry:
#clean up the idx, because the key expired
self.db.srem(id_idx, sess)
continue
else:
continue
continue
val = Storage(val)
#add a delete_record method (necessary for sessions2trash.py)
val.delete_record = RecordDeleter(
@@ -186,9 +199,14 @@ class MockQuery(object):
def update(self, **kwargs):
#means that the session has been found and needs an update
if self.op == 'eq' and self.field == 'id' and self.value:
rtn = self.db.hmset("%s:%s" % (self.keyprefix, self.value), kwargs)
if self.session_expiry:
self.db.expire(key, self.session.expiry)
key = "%s:%s" % (self.keyprefix, self.value)
with self.db.pipeline() as pipe:
pipe.hmset(key, kwargs)
if self.session_expiry:
pipe.expire(key, self.session_expiry)
rtn = pipe.execute()[0]
if self.with_lock:
release_lock(self.db, key + ':lock', self.value)
return rtn
@@ -204,3 +222,25 @@ class RecordDeleter(object):
self.db.srem(id_idx, self.key)
#remove the key itself
self.db.delete(self.key)
def acquire_lock(conn, lockname, identifier, ltime=10):
while True:
if conn.set(lockname, identifier, ex=ltime, nx=True):
return identifier
time.sleep(.01)
_LUA_RELEASE_LOCK = """
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
"""
def release_lock(conn, lockname, identifier):
return RedisClient._release_script(keys=[lockname], args=[identifier],
client=conn)
+27 -14
View File
@@ -23,6 +23,10 @@ class Stripe:
if paid is True than transaction was processed
"""
URL_CHARGE = 'https://%s:@api.stripe.com/v1/charges'
URL_CHECK = 'https://%s:@api.stripe.com/v1/charges/%s'
URL_REFUND = 'https://%s:@api.stripe.com/v1/charges/%s/refund'
def __init__(self, key):
self.key = key
@@ -33,27 +37,36 @@ class Stripe:
card_exp_month='5',
card_exp_year='2012',
card_cvc_check='123',
description='test charge'):
params = urllib.urlencode({'amount': amount,
'currency': currency,
'card[number]': card_number,
'card[exp_month]': card_exp_month,
'card[exp_year]': card_exp_year,
'card[cvc_check]': card_cvc_check,
'description': description})
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges' %
self.key, params)
token=None,
description='test charge',
more=None):
if token:
d = {'amount': amount,
'currency': currency,
'card': token,
'description': description}
else:
d = {'amount': amount,
'currency': currency,
'card[number]': card_number,
'card[exp_month]': card_exp_month,
'card[exp_year]': card_exp_year,
'card[cvc_check]': card_cvc_check,
'description': description}
if more:
d.update(mode)
params = urllib.urlencode(d)
u = urllib.urlopen(self.URL_CHARGE % self.key, params)
return simplejson.loads(u.read())
def check(self, charge_id):
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s' %
(self.key, charge_id))
u = urllib.urlopen(self.URL_CHECK % (self.key, charge_id))
return simplejson.loads(u.read())
def refund(self, charge_id):
params = urllib.urlencode({})
u = urllib.urlopen('https://%s:@api.stripe.com/v1/charges/%s/refund' %
(self.key, charge_id), params)
u = urllib.urlopen(self.URL_REFUND % (self.key, charge_id),
params)
return simplejson.loads(u.read())
if __name__ == '__main__':
+182 -76
View File
@@ -196,7 +196,7 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType,
TABLE_ARGS = set(
('migrate','primarykey','fake_migrate','format','redefine',
'singular','plural','trigger_name','sequence_name',
'common_filter','polymodel','table_class','on_define',))
'common_filter','polymodel','table_class','on_define','actual_name'))
SELECT_ARGS = set(
('orderby', 'groupby', 'limitby','required', 'cache', 'left',
@@ -266,7 +266,7 @@ REGEX_STORE_PATTERN = re.compile('\.(?P<e>\w{1,5})$')
REGEX_QUOTES = re.compile("'[^']*'")
REGEX_ALPHANUMERIC = re.compile('^[0-9a-zA-Z]\w*$')
REGEX_PASSWORD = re.compile('\://([^:@]*)\:')
REGEX_NOPASSWD = re.compile('(?<=\:)([^:@/]+)(?=@.+)')
REGEX_NOPASSWD = re.compile('\/\/[\w\.\-]+[\:\/](.+)(?=@)') # was '(?<=[\:\/])([^:@/]+)(?=@.+)'
# list of drivers will be built on the fly
# and lists only what is available
@@ -457,6 +457,8 @@ def pluralize(singular, rules=PLURALIZE_RULES):
if plural: return plural
def hide_password(uri):
if isinstance(uri,(list,tuple)):
return [hide_password(item) for item in uri]
return REGEX_NOPASSWD.sub('******',uri)
def OR(a,b):
@@ -470,6 +472,11 @@ def IDENTITY(x): return x
def varquote_aux(name,quotestr='%s'):
return name if REGEX_W.match(name) else quotestr % name
def quote_keyword(a,keyword='timestamp'):
regex = re.compile('\.keyword(?=\w)')
a = regex.sub('."%s"' % keyword,a)
return a
if 'google' in DRIVERS:
is_jdbc = False
@@ -583,11 +590,15 @@ class ConnectionPool(object):
if the connection is not active (closed by db server) it will loop
if not self.pool_size or no active connections in pool makes a new one
"""
if getattr(self,'connection',None) != None:
if getattr(self,'connection', None) != None:
return
if f is None:
f = self.connector
if not hasattr(self, "driver") or self.driver is None:
LOGGER.debug("Skipping connection since there's no driver")
return
if not self.pool_size:
self.connection = f()
self.cursor = cursor and self.connection.cursor()
@@ -917,7 +928,7 @@ class BaseAdapter(ConnectionPool):
foreign_key = ', '.join(pkeys),
on_delete_action = field.ondelete)
if hasattr(table,'_primarykey'):
if getattr(table,'_primarykey',None):
query = "CREATE TABLE %s(\n %s,\n %s) %s" % \
(tablename, fields,
self.PRIMARY_KEY(', '.join(table._primarykey)),other)
@@ -1184,7 +1195,7 @@ class BaseAdapter(ConnectionPool):
self.file_delete(table._dbt)
logfile.write('success!\n')
def _insert(self, table, fields):
def _insert(self, table, fields):
if fields:
keys = ','.join(f.name for f, v in fields)
values = ','.join(self.expand(v, f.type) for f, v in fields)
@@ -1255,7 +1266,7 @@ class BaseAdapter(ConnectionPool):
return '(%s LIKE %s)' % (self.expand(first),
self.expand('%'+second, 'string'))
def CONTAINS(self,first,second,case_sensitive=False):
def CONTAINS(self,first,second,case_sensitive=False):
if first.type in ('string','text', 'json'):
second = Expression(None,self.CONCAT('%',Expression(
None,self.REPLACE(second,('%','%%'))),'%'))
@@ -1306,7 +1317,7 @@ class BaseAdapter(ConnectionPool):
ftype.startswith('decimal')
def REPLACE(self, first, (second, third)):
return 'REPLACE(%s,%s,%s)' % (self.expand(first,'string'),
return 'REPLACE(%s,%s,%s)' % (self.expand(first,'string'),
self.expand(second,'string'),
self.expand(third,'string'))
@@ -1378,13 +1389,16 @@ class BaseAdapter(ConnectionPool):
else:
return str(expression)
def table_alias(self,name):
return str(name if isinstance(name,Table) else self.db[name])
def alias(self, table, alias):
"""
Given a table object, makes a new table object
with alias name.
"""
other = copy.copy(table)
other['_ot'] = other._tablename
other['_ot'] = other._ot or other._tablename
other['ALL'] = SQLALL(other)
other['_tablename'] = alias
for fieldname in other.fields:
@@ -1586,20 +1600,19 @@ class BaseAdapter(ConnectionPool):
query = self.common_filter(query,tablenames_for_common_filters)
sql_w = ' WHERE ' + self.expand(query) if query else ''
def alias(t):
return str(self.db[t])
if inner_join and not left:
sql_t = ', '.join([alias(t) for t in iexcluded + \
sql_t = ', '.join([self.table_alias(t) for t in iexcluded + \
itables_to_merge.keys()])
for t in ijoinon:
sql_t += ' %s %s' % (icommand, str(t))
sql_t += ' %s %s' % (icommand, t)
elif not inner_join and left:
sql_t = ', '.join([alias(t) for t in excluded + \
sql_t = ', '.join([self.table_alias(t) for t in excluded + \
tables_to_merge.keys()])
if joint:
sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
sql_t += ' %s %s' % (command,
','.join([self.table_alias(t) for t in joint]))
for t in joinon:
sql_t += ' %s %s' % (command, str(t))
sql_t += ' %s %s' % (command, t)
elif inner_join and left:
all_tables_in_query = set(important_tablenames + \
iimportant_tablenames + \
@@ -1607,15 +1620,16 @@ class BaseAdapter(ConnectionPool):
tables_in_joinon = set(joinont + ijoinont)
tables_not_in_joinon = \
all_tables_in_query.difference(tables_in_joinon)
sql_t = ','.join([alias(t) for t in tables_not_in_joinon])
sql_t = ','.join([self.table_alias(t) for t in tables_not_in_joinon])
for t in ijoinon:
sql_t += ' %s %s' % (icommand, str(t))
sql_t += ' %s %s' % (icommand, t)
if joint:
sql_t += ' %s %s' % (command, ','.join([t for t in joint]))
sql_t += ' %s %s' % (command,
','.join([self.table_alias(t) for t in joint]))
for t in joinon:
sql_t += ' %s %s' % (command, str(t))
sql_t += ' %s %s' % (command, t)
else:
sql_t = ', '.join(alias(t) for t in tablenames)
sql_t = ', '.join(self.table_alias(t) for t in tablenames)
if groupby:
if isinstance(groupby, (list, tuple)):
groupby = xorify(groupby)
@@ -1697,7 +1711,7 @@ class BaseAdapter(ConnectionPool):
sql_w = ' WHERE ' + self.expand(query)
else:
sql_w = ''
sql_t = ','.join(tablenames)
sql_t = ','.join(self.table_alias(t) for t in tablenames)
if distinct:
if isinstance(distinct,(list, tuple)):
distinct = xorify(distinct)
@@ -1755,11 +1769,13 @@ class BaseAdapter(ConnectionPool):
def log_execute(self, *a, **b):
if not self.connection: return None
command = a[0]
if hasattr(self,'filter_sql_command'):
command = self.filter_sql_command(command)
if self.db._debug:
LOGGER.debug('SQL: %s' % command)
self.db._lastsql = command
t0 = time.time()
ret = self.cursor.execute(*a, **b)
ret = self.cursor.execute(command, *a[1:], **b)
self.db._timings.append((command,time.time()-t0))
del self.db._timings[:-TIMINGSSIZE]
return ret
@@ -1803,7 +1819,7 @@ class BaseAdapter(ConnectionPool):
else:
return self.smart_adapt(self.FALSE)
if fieldtype == 'id' or fieldtype == 'integer':
return str(int(obj))
return str(long(obj))
if field_is_type('decimal'):
return str(obj)
elif field_is_type('reference'): # reference
@@ -1811,7 +1827,7 @@ class BaseAdapter(ConnectionPool):
return repr(obj)
elif isinstance(obj, (Row, Reference)):
return str(obj['id'])
return str(int(obj))
return str(long(obj))
elif fieldtype == 'double':
return repr(float(obj))
if isinstance(obj, unicode):
@@ -1840,7 +1856,7 @@ class BaseAdapter(ConnectionPool):
if have_serializers:
obj = serializers.json(obj)
elif simplejson:
obj = simplejson.dumps(items)
obj = simplejson.dumps(obj)
else:
raise RuntimeError("missing simplejson")
if not isinstance(obj,bytes):
@@ -1897,7 +1913,7 @@ class BaseAdapter(ConnectionPool):
return value
def parse_boolean(self, value, field_type):
return value == True or str(value)[:1].lower() == 't'
return value == self.TRUE or str(value)[:1].lower() == 't'
def parse_date(self, value, field_type):
if isinstance(value, datetime.datetime):
@@ -1968,10 +1984,10 @@ class BaseAdapter(ConnectionPool):
return value
def parse_id(self, value, field_type):
return int(value)
return long(value)
def parse_integer(self, value, field_type):
return int(value)
return long(value)
def parse_double(self, value, field_type):
return float(value)
@@ -2506,6 +2522,9 @@ class MySQLAdapter(BaseAdapter):
self.execute('select last_insert_id();')
return int(self.cursor.fetchone()[0])
def integrity_error_class(self):
return self.cursor.IntegrityError
class PostgreSQLAdapter(BaseAdapter):
drivers = ('psycopg2','pg8000')
@@ -3030,7 +3049,7 @@ class OracleAdapter(BaseAdapter):
def lastrowid(self,table):
sequence_name = table._sequence_name
self.execute('SELECT %s.currval FROM dual;' % sequence_name)
return int(self.cursor.fetchone()[0])
return long(self.cursor.fetchone()[0])
#def parse_value(self, value, field_type, blob_decode=True):
# if blob_decode and isinstance(value, cx_Oracle.LOB):
@@ -3195,7 +3214,7 @@ class MSSQLAdapter(BaseAdapter):
def lastrowid(self,table):
#self.execute('SELECT @@IDENTITY;')
self.execute('SELECT SCOPE_IDENTITY();')
return int(self.cursor.fetchone()[0])
return long(self.cursor.fetchone()[0])
def integrity_error_class(self):
return pyodbc.IntegrityError
@@ -3322,6 +3341,55 @@ class MSSQL2Adapter(MSSQLAdapter):
def execute(self,a):
return self.log_execute(a.decode('utf8'))
class VerticaAdapter(MSSQLAdapter):
drivers = ('pyodbc',)
T_SEP = ' '
types = {
'boolean': 'BOOLEAN',
'string': 'VARCHAR(%(length)s)',
'text': 'BYTEA',
'json': 'VARCHAR(%(length)s)',
'password': 'VARCHAR(%(length)s)',
'blob': 'BYTEA',
'upload': 'VARCHAR(%(length)s)',
'integer': 'INT',
'bigint': 'BIGINT',
'float': 'FLOAT',
'double': 'DOUBLE PRECISION',
'decimal': 'DECIMAL(%(precision)s,%(scale)s)',
'date': 'DATE',
'time': 'TIME',
'datetime': 'DATETIME',
'id': 'IDENTITY',
'reference': 'INT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'list:integer': 'BYTEA',
'list:string': 'BYTEA',
'list:reference': 'BYTEA',
'big-reference': 'BIGINT REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
}
def EXTRACT(self, first, what):
return "DATE_PART('%s', TIMESTAMP %s)" % (what, self.expand(first))
def _truncate(self, table, mode=''):
tablename = table._tablename
return ['TRUNCATE %s %s;' % (tablename, mode or '')]
def select_limitby(self, sql_s, sql_f, sql_t, sql_w, sql_o, limitby):
if limitby:
(lmin, lmax) = limitby
sql_o += ' LIMIT %i OFFSET %i' % (lmax - lmin, lmin)
return 'SELECT %s %s FROM %s%s%s;' % \
(sql_s, sql_f, sql_t, sql_w, sql_o)
def lastrowid(self,table):
self.execute('SELECT LAST_INSERT_ID();')
return long(self.cursor.fetchone()[0])
def execute(self, a):
return self.log_execute(a)
class SybaseAdapter(MSSQLAdapter):
drivers = ('Sybase',)
@@ -3544,7 +3612,7 @@ class FireBirdAdapter(BaseAdapter):
def lastrowid(self,table):
sequence_name = table._sequence_name
self.execute('SELECT gen_id(%s, 0) FROM rdb$database' % sequence_name)
return int(self.cursor.fetchone()[0])
return long(self.cursor.fetchone()[0])
class FireBirdEmbeddedAdapter(FireBirdAdapter):
@@ -3801,7 +3869,7 @@ class DB2Adapter(BaseAdapter):
def lastrowid(self,table):
self.execute('SELECT DISTINCT IDENTITY_VAL_LOCAL() FROM %s;' % table)
return int(self.cursor.fetchone()[0])
return long(self.cursor.fetchone()[0])
def rowslice(self,rows,minimum=0,maximum=None):
if maximum is None:
@@ -3980,7 +4048,7 @@ class IngresAdapter(BaseAdapter):
def lastrowid(self,table):
tmp_seqname='%s_iisq' % table
self.execute('select current value for %s' % tmp_seqname)
return int(self.cursor.fetchone()[0]) # don't really need int type cast here...
return long(self.cursor.fetchone()[0]) # don't really need int type cast here...
def integrity_error_class(self):
return self._driver.IntegrityError
@@ -4106,7 +4174,7 @@ class SAPDBAdapter(BaseAdapter):
def lastrowid(self,table):
self.execute("select %s.NEXTVAL from dual" % table._sequence_name)
return int(self.cursor.fetchone()[0])
return long(self.cursor.fetchone()[0])
class CubridAdapter(MySQLAdapter):
drivers = ('cubriddb',)
@@ -4719,12 +4787,17 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
"text and blob field types not allowed in projection queries")
else:
projection.append(f.name)
elif args_get('filterfields') == True:
projection = []
for f in fields:
projection.append(f.name)
# projection's can't include 'id'.
# real projection's can't include 'id'.
# it will be added to the result later
query_projection = [
p for p in projection if \
p != db[tablename]._id.name] if projection \
p != db[tablename]._id.name] if projection and \
args_get('projection') == True\
else None
cursor = None
@@ -4802,6 +4875,11 @@ class GoogleDatastoreAdapter(NoSQLAdapter):
what is accepted imposed by GAE: each field must be indexed,
projection queries cannot contain blob or text fields, and you
cannot use == and also select that same field. see https://developers.google.com/appengine/docs/python/datastore/queries#Query_Projection
- optional attribute 'filterfields' when set to True web2py will only
parse the explicitly listed fields into the Rows object, even though
all fields are returned in the query. This can be used to reduce
memory usage in cases where true projection queries are not
usable.
- optional attribute 'reusecursor' allows use of cursor with queries
that have the limitby attribute. Set the attribute to True for the
first query, set it to the value of db['_lastcursor'] to continue
@@ -4945,7 +5023,7 @@ class CouchDBAdapter(NoSQLAdapter):
def represent(self, obj, fieldtype):
value = NoSQLAdapter.represent(self, obj, fieldtype)
if fieldtype=='id':
return repr(str(int(value)))
return repr(str(long(value)))
elif fieldtype in ('date','time','datetime','boolean'):
return serializers.json(value)
return repr(not isinstance(value,unicode) and value \
@@ -4999,7 +5077,7 @@ class CouchDBAdapter(NoSQLAdapter):
def uid(fd):
return fd=='id' and '_id' or fd
def get(row,fd):
return fd=='id' and int(row['_id']) or row.get(fd,None)
return fd=='id' and long(row['_id']) or row.get(fd,None)
fields = new_fields
tablename = self.get_table(query)
fieldnames = [f.name for f in (fields or self.db[tablename])]
@@ -5212,13 +5290,13 @@ class MongoDBAdapter(NoSQLAdapter):
def parse_reference(self, value, field_type):
# here we have to check for ObjectID before base parse
if isinstance(value, self.ObjectId):
value = int(str(value), 16)
value = long(str(value), 16)
return super(MongoDBAdapter,
self).parse_reference(value, field_type)
def parse_id(self, value, field_type):
if isinstance(value, self.ObjectId):
value = int(str(value), 16)
value = long(str(value), 16)
return super(MongoDBAdapter,
self).parse_id(value, field_type)
@@ -5277,7 +5355,7 @@ class MongoDBAdapter(NoSQLAdapter):
else:
values[fieldname] = self.represent(v, fieldtype)
ctable.insert(values, safe=safe)
return int(str(values['_id']), 16)
return long(str(values['_id']), 16)
def create_table(self, table, migrate=True, fake_migrate=False,
polymodel=None, isCapped=False):
@@ -5290,8 +5368,8 @@ class MongoDBAdapter(NoSQLAdapter):
if not isinstance(query,Query):
raise SyntaxError("Not Supported")
tablename = self.get_table(query)
return int(self.select(query,[self.db[tablename]._id], {},
count=True,snapshot=snapshot)['count'])
return long(self.select(query,[self.db[tablename]._id], {},
count=True,snapshot=snapshot)['count'])
# Maybe it would be faster if we just implemented the pymongo
# .count() function which is probably quicker?
# therefor call __select() connection[table].find(query).count()
@@ -6214,7 +6292,7 @@ class IMAPAdapter(NoSQLAdapter):
typ, data = self.connection.uid("fetch", uid, imap_fields)
if typ == "OK":
fr = {"message": int(data[0][0].split()[0]),
"uid": int(uid),
"uid": long(uid),
"email": email.message_from_string(data[0][1]),
"raw_message": data[0][1]}
fr["multipart"] = fr["email"].is_multipart()
@@ -6644,6 +6722,7 @@ ADAPTERS = {
'mssql': MSSQLAdapter,
'mssql2': MSSQL2Adapter,
'mssql3': MSSQL3Adapter,
'vertica': VerticaAdapter,
'sybase': SybaseAdapter,
'db2': DB2Adapter,
'teradata': TeradataAdapter,
@@ -6666,7 +6745,6 @@ ADAPTERS = {
'imap': IMAPAdapter
}
def sqlhtml_validators(field):
"""
Field type validation, using web2py's validators mechanism.
@@ -6774,7 +6852,7 @@ def bar_encode(items):
def bar_decode_integer(value):
if not hasattr(value,'split') and hasattr(value,'read'):
value = value.read()
return [int(x) for x in value.split('|') if x.strip()]
return [long(x) for x in value.split('|') if x.strip()]
def bar_decode_string(value):
return [x.replace('||', '|') for x in
@@ -6851,6 +6929,9 @@ class Row(object):
def __int__(self):
return object.__getattribute__(self,'id')
def __long__(self):
return long(object.__getattribute__(self,'id'))
def __eq__(self,other):
try:
return self.as_dict() == other.as_dict()
@@ -6877,7 +6958,7 @@ class Row(object):
elif isinstance(v,Row):
d[k]=v.as_dict()
elif isinstance(v,Reference):
d[k]=int(v)
d[k]=long(v)
elif isinstance(v,decimal.Decimal):
d[k]=float(v)
elif isinstance(v, (datetime.date, datetime.datetime, datetime.time)):
@@ -7653,8 +7734,8 @@ def index():
'error':'I\'m a teapot','response':None})
try:
distinct = vars.get('distinct', False) == 'True'
offset = int(vars.get('offset',None) or 0)
limits = (offset,int(vars.get('limit',None) or 1000)+offset)
offset = long(vars.get('offset',None) or 0)
limits = (offset,long(vars.get('limit',None) or 1000)+offset)
except ValueError:
return Row({'status':400,'error':'invalid limits','response':None})
items = dbset.select(db[table][field], distinct=distinct, limitby=limits)
@@ -7680,8 +7761,8 @@ def index():
fields = [field for field in db[table] if field.readable]
count = dbset.count()
try:
offset = int(vars.get('offset',None) or 0)
limits = (offset,int(vars.get('limit',None) or 1000)+offset)
offset = long(vars.get('offset',None) or 0)
limits = (offset,long(vars.get('limit',None) or 1000)+offset)
except ValueError:
return Row({'status':400,'error':'invalid limits','response':None})
if count > limits[1]-limits[0]:
@@ -7975,7 +8056,7 @@ def index():
if not field.table==thistable]
def export_to_csv_file(self, ofile, *args, **kwargs):
step = int(kwargs.get('max_fetch_rows,',500))
step = long(kwargs.get('max_fetch_rows,',500))
write_colnames = kwargs['write_colnames'] = \
kwargs.get("write_colnames", True)
for table in self.tables:
@@ -8034,14 +8115,14 @@ class Reference(long):
def __allocate(self):
if not self._record:
self._record = self._table[int(self)]
self._record = self._table[long(self)]
if not self._record:
raise RuntimeError(
"Using a recursive select but encountered a broken reference: %s %d"%(self._table, int(self)))
"Using a recursive select but encountered a broken reference: %s %d"%(self._table, long(self)))
def __getattr__(self, key):
if key == 'id':
return int(self)
return long(self)
self.__allocate()
return self._record.get(key, None)
@@ -8050,14 +8131,14 @@ class Reference(long):
def __setattr__(self, key, value):
if key.startswith('_'):
int.__setattr__(self, key, value)
long.__setattr__(self, key, value)
return
self.__allocate()
self._record[key] = value
def __getitem__(self, key):
if key == 'id':
return int(self)
return long(self)
self.__allocate()
return self._record.get(key, None)
@@ -8071,13 +8152,29 @@ def Reference_unpickler(data):
def Reference_pickler(data):
try:
marshal_dump = marshal.dumps(int(data))
marshal_dump = marshal.dumps(long(data))
except AttributeError:
marshal_dump = 'i%s' % struct.pack('<i', int(data))
marshal_dump = 'i%s' % struct.pack('<i', long(data))
return (Reference_unpickler, (marshal_dump,))
copyreg.pickle(Reference, Reference_pickler, Reference_unpickler)
class MethodAdder(object):
def __init__(self,table):
self.table = table
def __call__(self):
return self.register()
def __getattr__(self,method_name):
return self.register(method_name)
def register(self,method_name=None):
def _decorated(f):
instance = self.table
import types
method = types.MethodType(f, instance, instance.__class__)
name = method_name or f.func_name
setattr(instance, name, method)
return f
return _decorated
class Table(object):
@@ -8111,19 +8208,20 @@ class Table(object):
"""
self._actual = False # set to True by define_table()
self._tablename = tablename
self._sequence_name = args.get('sequence_name',None) or \
self._ot = args.get('actual_name')
self._sequence_name = args.get('sequence_name') or \
db and db._adapter.sequence_name(tablename)
self._trigger_name = args.get('trigger_name',None) or \
self._trigger_name = args.get('trigger_name') or \
db and db._adapter.trigger_name(tablename)
self._common_filter = args.get('common_filter', None)
self._format = args.get('format',None)
self._common_filter = args.get('common_filter')
self._format = args.get('format')
self._singular = args.get(
'singular',tablename.replace('_',' ').capitalize())
self._plural = args.get(
'plural',pluralize(self._singular.lower()).capitalize())
# horrible but for backard compatibility of appamdin:
if 'primarykey' in args and args['primarykey']:
self._primarykey = args.get('primarykey', None)
if 'primarykey' in args and args['primarykey'] is not None:
self._primarykey = args.get('primarykey')
self._before_insert = []
self._before_update = [Set.delete_uploaded_files]
@@ -8132,6 +8230,8 @@ class Table(object):
self._after_update = []
self._after_delete = []
self.add_method = MethodAdder(self)
fieldnames,newfields=set(),[]
if hasattr(self,'_primarykey'):
if not isinstance(self._primarykey,list):
@@ -8417,8 +8517,8 @@ class Table(object):
def __repr__(self):
return '<Table %s (%s)>' % (self._tablename,','.join(self.fields()))
def __str__(self):
if hasattr(self,'_ot') and self._ot is not None:
def __str__(self):
if self._ot is not None:
if 'Oracle' in str(type(self._db._adapter)): # <<< patch
return '%s %s' % (self._ot, self._tablename) # <<< patch
return '%s AS %s' % (self._ot, self._tablename)
@@ -8618,13 +8718,13 @@ class Table(object):
if not value.strip():
value = None
else:
value = int(value)
value = long(value)
elif field.type.startswith('list:string'):
value = bar_decode_string(value)
elif field.type.startswith(list_reference_s):
ref_table = field.type[len(list_reference_s):].strip()
if id_map is not None:
value = [id_map[ref_table][int(v)] \
value = [id_map[ref_table][long(v)] \
for v in bar_decode_string(value)]
else:
value = [v for v in bar_decode_string(value)]
@@ -8632,12 +8732,12 @@ class Table(object):
value = bar_decode_integer(value)
elif id_map and field.type.startswith('reference'):
try:
value = id_map[field.type[9:].strip()][int(value)]
value = id_map[field.type[9:].strip()][long(value)]
except KeyError:
pass
elif id_offset and field.type.startswith('reference'):
try:
value = id_offset[field.type[9:].strip()]+int(value)
value = id_offset[field.type[9:].strip()]+long(value)
except KeyError:
pass
return (field.name, value)
@@ -8668,7 +8768,7 @@ class Table(object):
for i in cols if colnames[i] in self.fields]
if not id_map and cid is not None and id_offset is not None and not unique_idx:
csv_id = int(line[cid])
csv_id = long(line[cid])
curr_id = self.insert(**dict(items))
if first:
first = False
@@ -8697,7 +8797,7 @@ class Table(object):
else:
new_id = self.insert(**dict(items))
if id_map and cid is not None:
id_map_self[int(line[cid])] = new_id
id_map_self[long(line[cid])] = new_id
def as_dict(self, flat=False, sanitize=True, field_options=True):
tablename = str(self)
@@ -9575,9 +9675,13 @@ class Query(object):
def __and__(self, other):
return Query(self.db,self.db._adapter.AND,self,other)
__rand__ = __and__
def __or__(self, other):
return Query(self.db,self.db._adapter.OR,self,other)
__ror__ = __or__
def __invert__(self):
if self.op==self.db._adapter.NOT:
return self.first
@@ -9707,7 +9811,9 @@ class Set(object):
return '<Set %s>' % BaseAdapter.expand(self.db._adapter,self.query)
def __call__(self, query, ignore_common_filters=False):
if isinstance(query,Table):
if query is None:
return self
elif isinstance(query,Table):
query = self.db._adapter.id_query(query)
elif isinstance(query,str):
query = Expression(self.db,query)
@@ -9833,7 +9939,7 @@ class Set(object):
return built
def isempty(self):
return not self.select(limitby=(0,1))
return not self.select(limitby=(0,1), orderby_on_limitby=False)
def count(self,distinct=None, cache=None):
db = self.db
@@ -10314,7 +10420,7 @@ class Rows(object):
elif isinstance(value, unicode):
return value.encode('utf8')
elif isinstance(value,Reference):
return int(value)
return long(value)
elif hasattr(value, 'isoformat'):
return value.isoformat()[:19].replace('T', ' ')
elif isinstance(value, (list,tuple)): # for type='list:..'
+6 -5
View File
@@ -586,7 +586,7 @@ class XML(XmlComponent):
return self.text
def __str__(self):
return self.xml()
return self.text
def __add__(self, other):
return '%s%s' % (self, other)
@@ -600,8 +600,9 @@ class XML(XmlComponent):
def __hash__(self):
return hash(str(self))
def __getattr__(self, name):
return getattr(str(self), name)
# why was this here? Break unpickling in sessions
# def __getattr__(self, name):
# return getattr(str(self), name)
def __getitem__(self, i):
return str(self)[i]
@@ -1991,10 +1992,10 @@ class FORM(DIV):
status = True
changed = False
request_vars = self.request_vars
if session:
if session is not None:
formkey = session.get('_formkey[%s]' % formname, None)
# check if user tampering with form and void CSRF
if formkey != request_vars._formkey:
if not formkey or formkey != request_vars._formkey:
status = False
if formname != request_vars._formname:
status = False
+6 -5
View File
@@ -123,14 +123,15 @@ class HTTP(BaseException):
message elements that are not defined are omitted
"""
msg = '%(status)d'
msg = '%(status)s'
if self.status in defined_status:
msg = '%(status)d %(defined_status)s'
msg = '%(status)s %(defined_status)s'
if 'web2py_error' in self.headers:
msg += ' [%(web2py_error)s]'
return msg % dict(status=self.status,
defined_status=defined_status.get(self.status),
web2py_error=self.headers.get('web2py_error'))
return msg % dict(
status=self.status,
defined_status=defined_status.get(self.status),
web2py_error=self.headers.get('web2py_error'))
def __str__(self):
"stringify me"
+39 -8
View File
@@ -29,6 +29,14 @@ import tempfile
import random
import string
import urllib2
try:
import simplejson as sj #external installed library
except:
try:
import json as sj #standard installed library
except:
import contrib.simplejson as sj #pure python library
from thread import allocate_lock
from fileutils import abspath, write_file, parse_version, copystream
@@ -87,7 +95,7 @@ from settings import global_settings
from validators import CRYPT
from cache import CacheInRam
from html import URL, xmlescape
from utils import is_valid_ip_address
from utils import is_valid_ip_address, getipaddrinfo
from rewrite import load, url_in, THREAD_LOCAL as rwthread, \
try_rewrite_on_error, fixup_missing_path_info
import newcron
@@ -295,6 +303,7 @@ def environ_aux(environ, request):
new_environ['wsgi.version'] = 1
return new_environ
ISLE25 = sys.version_info[1] <= 5
def parse_get_post_vars(request, environ):
@@ -311,17 +320,36 @@ def parse_get_post_vars(request, environ):
request.get_vars[key] = value
request.vars[key] = request.get_vars[key]
# parse POST variables on POST, PUT, BOTH only in post_vars
try:
request.body = body = copystream_progress(request)
except IOError:
raise HTTP(400, "Bad Request - HTTP body is incomplete")
#if content-type is application/json, we must read the body
is_json = env.get('http_content_type', '')[:16] == 'application/json'
if is_json:
try:
json_vars = sj.load(body)
body.seek(0)
except:
# incoherent request bodies can still be parsed "ad-hoc"
json_vars = {}
pass
# update vars and get_vars with what was posted as json
request.get_vars.update(json_vars)
request.vars.update(json_vars)
# parse POST variables on POST, PUT, BOTH only in post_vars
if (body and env.request_method in ('POST', 'PUT', 'BOTH')):
dpost = cgi.FieldStorage(fp=body, environ=environ, keep_blank_values=1)
# The same detection used by FieldStorage to detect multipart POSTs
is_multipart = dpost.type[:10] == 'multipart/'
body.seek(0)
isle25 = sys.version_info[1] <= 5
def listify(a):
return (not isinstance(a, list) and [a]) or a
@@ -348,7 +376,7 @@ def parse_get_post_vars(request, environ):
pvalue = listify(value)
if key in request.vars:
gvalue = listify(request.vars[key])
if isle25:
if ISLE25:
value = pvalue + gvalue
elif is_multipart:
pvalue = pvalue[len(gvalue):]
@@ -358,6 +386,9 @@ def parse_get_post_vars(request, environ):
if len(pvalue):
request.post_vars[key] = (len(pvalue) >
1 and pvalue) or pvalue[0]
if is_json:
# update post_vars with what was posted as json
request.post_vars.update(json_vars)
def wsgibase(environ, responder):
@@ -440,13 +471,13 @@ def wsgibase(environ, responder):
local_hosts.add(socket.gethostname())
local_hosts.add(fqdn)
local_hosts.update([
ip[4][0] for ip in socket.getaddrinfo(
fqdn, 0)])
addrinfo[4][0] for addrinfo
in getipaddrinfo(fqdn)])
if env.server_name:
local_hosts.add(env.server_name)
local_hosts.update([
ip[4][0] for ip in socket.getaddrinfo(
env.server_name, 0)])
addrinfo[4][0] for addrinfo
in getipaddrinfo(env.server_name)])
except (socket.gaierror, TypeError):
pass
global_settings.local_hosts = list(local_hosts)
+6 -4
View File
@@ -12,10 +12,10 @@ from languages import lazyT
import contrib.rss2 as rss2
try:
import json as json_parser # try stdlib (Python 2.6)
import simplejson as json_parser # try external module
except ImportError:
try:
import simplejson as json_parser # try external module
import json as json_parser # try stdlib (Python >= 2.6)
except:
import contrib.simplejson as json_parser # fallback to pure-Python module
@@ -127,7 +127,8 @@ def csv(value):
return ''
def ics(events, title=None, link=None, timeshift=0, **ignored):
def ics(events, title=None, link=None, timeshift=0, calname=True,
**ignored):
import datetime
title = title or '(unkown)'
if link and not callable(link):
@@ -135,7 +136,8 @@ def ics(events, title=None, link=None, timeshift=0, **ignored):
'[id]', str(item['id']))
s = 'BEGIN:VCALENDAR'
s += '\nVERSION:2.0'
s += '\nX-WR-CALNAME:%s' % title
if not calname is False:
s += '\nX-WR-CALNAME:%s' % (calname or title)
s += '\nSUMMARY:%s' % title
s += '\nPRODID:Generated by web2py'
s += '\nCALSCALE:GREGORIAN'
+77 -37
View File
@@ -21,7 +21,7 @@ import os
from http import HTTP
from html import XmlComponent
from html import XML, SPAN, TAG, A, DIV, CAT, UL, LI, TEXTAREA, BR, IMG, SCRIPT
from html import FORM, INPUT, LABEL, OPTION, SELECT, BUTTON
from html import FORM, INPUT, LABEL, OPTION, SELECT
from html import TABLE, THEAD, TBODY, TR, TD, TH, STYLE
from html import URL, truncate_string, FIELDSET
from dal import DAL, Field, Table, Row, CALLABLETYPES, smart_query, \
@@ -36,7 +36,7 @@ import datetime
import urllib
import re
import cStringIO
from gluon import current, redirect, A, URL, DIV, H3, UL, LI, SPAN, INPUT
from gluon import current, redirect
import inspect
import settings
is_gae = settings.global_settings.web2py_runtime_gae
@@ -1073,7 +1073,7 @@ class SQLFORM(FORM):
cond = readonly or \
(not ignore_rw and not field.writable and field.readable)
if default and not cond:
if default is not None and not cond:
default = field.formatter(default)
dspval = default
inpval = default
@@ -1456,8 +1456,9 @@ class SQLFORM(FORM):
if not f:
continue
else:
f = os.path.join(current.request.folder,
os.path.normpath(f))
f = os.path.join(
current.request.folder,
os.path.normpath(f))
source_file = open(f, 'rb')
original_filename = os.path.split(f)[1]
elif hasattr(f, 'file'):
@@ -1466,6 +1467,10 @@ class SQLFORM(FORM):
### do not know why this happens, it should not
(source_file, original_filename) = \
(cStringIO.StringIO(f), 'file.txt')
else:
# this should never happen, why does it happen?
print 'f=',repr(f)
continue
newfilename = field.store(source_file, original_filename,
field.uploadfolder)
# this line was for backward compatibility but problematic
@@ -1648,7 +1653,14 @@ class SQLFORM(FORM):
selectfields = []
for field in fields:
name = str(field).replace('.', '-')
options = search_options.get(field.type.split(' ')[0], None)
# treat ftype 'decimal' as 'double'
# (this fixes problems but needs refactoring!
ftype = field.type.split(' ')[0]
if ftype.startswith('decimal'): ftype = 'double'
elif ftype=='bigint': ftype = 'integer'
elif ftype.startswith('big-'): ftype = ftype[4:]
# end
options = search_options.get(ftype, None)
if options:
label = isinstance(
field.label, str) and T(field.label) or field.label
@@ -1773,7 +1785,9 @@ class SQLFORM(FORM):
selectable_submit_button='Submit',
buttons_placement = 'right',
links_placement = 'right',
noconfirm=False
noconfirm=False,
cache_count=None,
client_side_delete=False,
):
# jQuery UI ThemeRoller classes (empty if ui is disabled)
@@ -1826,6 +1840,33 @@ class SQLFORM(FORM):
create = wenabled and create
editable = wenabled and editable
deletable = wenabled and deletable
rows = None
def fetch_count(dbset):
##FIXME for google:datastore cache_count is ignored
## if it's not an integer
if cache_count is None or isinstance(cache_count, tuple):
if groupby:
c = 'count(*)'
nrows = db.executesql(
'select count(*) from (%s);' %
dbset._select(c, left=left, cacheable=True,
groupby=groupby, cache=cache_count)[:-1])[0][0]
elif left:
c = 'count(*)'
nrows = dbset.select(c, left=left, cacheable=True, cache=cache_count).first()[c]
elif dbset._db._adapter.dbengine=='google:datastore':
#if we don't set a limit, this can timeout for a large table
nrows = dbset.db._adapter.count(dbset.query, limit=1000)
else:
nrows = dbset.count(cache=cache_count)
elif isinstance(cache_count, (int, long)):
nrows = cache_count
elif callable(cache_count):
nrows = cache_count(dbset, request.vars)
else:
nrows = 0
return nrows
def url(**b):
b['args'] = args + b.get('args', [])
@@ -1865,7 +1906,7 @@ class SQLFORM(FORM):
delete=None, trap=True, noconfirm=None):
if showbuttontext:
return A(SPAN(_class=ui.get(buttonclass)),
SPAN(T(buttontext), _title=buttontext,
SPAN(T(buttontext), _title=T(buttontext),
_class=ui.get('buttontext')),
_href=buttonurl,
callback=callback,
@@ -1878,7 +1919,7 @@ class SQLFORM(FORM):
callback=callback,
delete=delete,
noconfirm=noconfirm,
_title=buttontext,
_title=T(buttontext),
_class=trap_class(ui.get('buttontext'), trap))
dbset = db(query)
@@ -1957,6 +1998,7 @@ class SQLFORM(FORM):
res.update_form = update_form
res.view_form = view_form
res.search_form = search_form
res.rows = None
return res
elif details and request.args(-3) == 'view':
@@ -1973,15 +2015,17 @@ class SQLFORM(FORM):
res.update_form = update_form
res.view_form = view_form
res.search_form = search_form
res.rows = None
return res
elif editable and request.args(-3) == 'edit':
table = db[request.args[-2]]
record = table(request.args[-1]) or redirect(URL('error'))
sqlformargs.update(editargs)
deletable_ = deletable(record) if callable(deletable) else deletable
update_form = SQLFORM(
table,
record, upload=upload, ignore_rw=ignore_rw,
formstyle=formstyle, deletable=deletable,
formstyle=formstyle, deletable=deletable_,
_class='web2py_form',
submit_button=T('Submit'),
delete_label=T('Check to delete'),
@@ -1998,13 +2042,21 @@ class SQLFORM(FORM):
res.update_form = update_form
res.view_form = view_form
res.search_form = search_form
res.rows = None
return res
elif deletable and request.args(-3) == 'delete':
table = db[request.args[-2]]
if ondelete:
ondelete(table, request.args[-1])
db(table[table._id.name] == request.args[-1]).delete()
redirect(referrer)
if not callable(deletable):
if ondelete:
ondelete(table, request.args[-1])
db(table[table._id.name] == request.args[-1]).delete()
else:
record = table(request.args[-1]) or redirect(URL('error'))
if deletable(record):
if ondelete:
ondelete(table, request.args[-1])
record.delete_record()
redirect(referrer, client_side=client_side_delete)
exportManager = dict(
csv_with_hidden_cols=(ExporterCSV, 'CSV (hidden cols)'),
@@ -2055,7 +2107,7 @@ class SQLFORM(FORM):
else:
rows = dbset.select(left=left, orderby=orderby,
cacheable=True, *expcolumns)
value = exportManager[export_type]
clazz = value[0] if hasattr(value, '__getitem__') else value
oExp = clazz(rows)
@@ -2122,20 +2174,7 @@ class SQLFORM(FORM):
if subquery:
dbset = dbset(subquery)
try:
if groupby:
c = 'count(*)'
nrows = db.executesql(
'select count(*) from (%s);' %
dbset._select(c, left=left, cacheable=True,
groupby=groupby)[:-1])[0][0]
elif left:
c = 'count(*)'
nrows = dbset.select(c, left=left, cacheable=True).first()[c]
elif dbset._db._adapter.dbengine=='google:datastore':
#if we don't set a limit, this can timeout for a large table
nrows = dbset.db._adapter.count(dbset.query, limit=1000)
else:
nrows = dbset.count()
nrows = fetch_count(dbset)
except:
nrows = 0
error = T('Unsupported query')
@@ -2236,7 +2275,7 @@ class SQLFORM(FORM):
message = T('at least %(nrows)s records found') % dict(nrows=nrows)
else:
message = T('%(nrows)s records found') % dict(nrows=nrows)
console.append(DIV(message,_class='web2py_counter'))
console.append(DIV(message or T('None'),_class='web2py_counter'))
paginator = UL()
if paginate and dbset._db._adapter.dbengine=='google:datastore':
@@ -2323,9 +2362,9 @@ class SQLFORM(FORM):
if value:
if callable(upload):
value = A(
current.T('file'), _href=upload(value))
T('file'), _href=upload(value))
elif upload:
value = A(current.T('file'),
value = A(T('file'),
_href='%s/%s' % (upload, value))
else:
value = ''
@@ -2397,7 +2436,7 @@ class SQLFORM(FORM):
selectable(records)
redirect(referrer)
else:
htmltable = DIV(current.T('No records found'))
htmltable = DIV(T('No records found'))
if csv and nrows:
export_links = []
@@ -2427,6 +2466,7 @@ class SQLFORM(FORM):
res.update_form = update_form
res.view_form = view_form
res.search_form = search_form
res.rows = rows
return res
@staticmethod
@@ -2486,7 +2526,7 @@ class SQLFORM(FORM):
name = None
def format(table,row):
if not row:
return 'Unknown'
return T('Unknown')
elif isinstance(table._format,str):
return table._format % row
elif callable(table._format):
@@ -2616,11 +2656,11 @@ class SQLFORM(FORM):
SPAN(divider, _class='divider') if next else '',
_class='active w2p_grid_breadcrumb_elem'))
if grid.create_form:
header = T('New %s' % table._singular)
header = T('New %(entity)s') % dict(entity=table._singular)
elif grid.update_form:
header = T('Edit %s' % format(table,grid.update_form.record))
header = T('Edit %(entity)s') % dict(entity=format(table,grid.update_form.record))
elif grid.view_form:
header = T('View %s' % format(table,grid.view_form.record))
header = T('View %(entity)s') % dict(entity=format(table,grid.view_form.record))
if next:
breadcrumbs.append(LI(
A(T(header), _class=trap_class(),_href=url()),
+5 -2
View File
@@ -18,6 +18,7 @@ import portalocker
__all__ = ['List', 'Storage', 'Settings', 'Messages',
'StorageList', 'load_storage', 'save_storage']
DEFAULT = lambda:0
class Storage(dict):
"""
@@ -250,7 +251,7 @@ class List(list):
instead of IndexOutOfBounds
"""
def __call__(self, i, default=None, cast=None, otherwise=None):
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
"""
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
@@ -258,8 +259,10 @@ class List(list):
n = len(self)
if 0 <= i < n or -n <= i < 0:
value = self[i]
elif default is DEFAULT:
value = None
else:
value = default
value, cast = default, False
if cast:
try:
value = cast(value)
+5 -4
View File
@@ -562,8 +562,8 @@ class TemplateParser(object):
if in_tag:
line = i
# Get rid of '{{' and '}}'
line = line[2:-2].strip()
# Get rid of delimiters
line = line[len(self.delimiters[0]):-len(self.delimiters[1])].strip()
# This is bad juju, but let's do it anyway
if not line:
@@ -832,7 +832,8 @@ def render(content="hello world",
path=None,
context={},
lexers={},
delimiters=('{{', '}}')
delimiters=('{{', '}}'),
writer='response.write'
):
"""
>>> render()
@@ -895,7 +896,7 @@ def render(content="hello world",
# Execute the template.
code = str(TemplateParser(stream.read(
), context=context, path=path, lexers=lexers, delimiters=delimiters))
), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer))
try:
exec(code) in context
except Exception:
+5 -1
View File
@@ -1,3 +1,4 @@
from test_http import *
from test_cache import *
from test_dal import *
from test_html import *
@@ -9,5 +10,8 @@ from test_storage import *
from test_template import *
from test_utils import *
from test_contribs import *
from test_markmin import *
from test_web import *
import sys
if sys.version[:3] == '2.7':
from test_old_doctests import *
+30
View File
@@ -0,0 +1,30 @@
[run]
branch = True
parallel = True
source = gluon
## remove comment if you want applications coverage too
#source = gluon, applications
[report]
# Regexes for lines to exclude from consideration
exclude_lines =
# Have to re-enable the standard pragma
pragma: no cover
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:
ignore_errors = True
omit = gluon/contrib/*
[html]
directory = coverage_html_report
+17
View File
@@ -236,9 +236,12 @@ class TestInsert(unittest.TestCase):
self.assertEqual(db.tt.insert(aa='1'), 2)
self.assertEqual(db.tt.insert(aa='1'), 3)
self.assertEqual(db(db.tt.aa == '1').count(), 3)
self.assertEqual(db(db.tt.aa == '2').isempty(), True)
self.assertEqual(db(db.tt.aa == '1').update(aa='2'), 3)
self.assertEqual(db(db.tt.aa == '2').count(), 3)
self.assertEqual(db(db.tt.aa == '2').isempty(), False)
self.assertEqual(db(db.tt.aa == '2').delete(), 3)
self.assertEqual(db(db.tt.aa == '2').isempty(), True)
db.tt.drop()
@@ -272,6 +275,20 @@ class TestSelect(unittest.TestCase):
self.assertEqual(db(~(db.tt.aa > '1') & (db.tt.aa > '2')).count(), 0)
db.tt.drop()
class TestAddMethod(unittest.TestCase):
def testRun(self):
db = DAL(DEFAULT_URI, check_reserved=['all'])
db.define_table('tt', Field('aa'))
@db.tt.add_method.all
def select_all(table,orderby=None):
return table._db(table).select(orderby=orderby)
self.assertEqual(db.tt.insert(aa='1'), 1)
self.assertEqual(db.tt.insert(aa='2'), 2)
self.assertEqual(db.tt.insert(aa='3'), 3)
self.assertEqual(len(db.tt.all()), 3)
db.tt.drop()
class TestBelongs(unittest.TestCase):
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Unit tests for http.py """
import sys
import os
import unittest
if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon'))
else:
sys.path.append(os.path.realpath('../'))
from http import HTTP, defined_status
class TestHTTP(unittest.TestCase):
""" Tests http.HTTP """
def test_status_message(self):
""" Tests http status code message """
h = HTTP
def gen_status_str(code, message):
return str(code) + ' ' + str(message)
message = '1423 This is a custom message'
code = 1423
self.assertEqual(str(h(gen_status_str(code, message))),
gen_status_str(code, message))
# test predefined codes
for code in defined_status.keys():
self.assertEqual(
str(h(code)),
gen_status_str(code, defined_status[code]))
# test correct use of status_message
for code in defined_status.keys():
self.assertEqual(str(h(gen_status_str(code, message))),
gen_status_str(code, message))
# test wrong call detection
if __name__ == '__main__':
unittest.main()
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Unit tests for running web2py
"""
import sys
import os
if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon'))
else:
sys.path.append(os.path.realpath('../'))
import unittest
from contrib.markmin.markmin2html import run_doctests
class TestMarkmin(unittest.TestCase):
def testMarkmin(self):
run_doctests()
if __name__ == '__main__':
unittest.main()
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Unit tests for old doctests in validators.py, utf8.py, html.py,
markmin2html.py.
Don't abuse doctests, web2py > 2.4.5 will accept only unittests
"""
import sys
import os
if os.path.isdir('gluon'):
sys.path.append(os.path.realpath('gluon'))
else:
sys.path.append(os.path.realpath('../'))
import unittest
import doctest
def load_tests(loader, tests, ignore):
tests.addTests(
doctest.DocTestSuite('validators',
optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS,
)
)
tests.addTests(
doctest.DocTestSuite('html')
)
tests.addTests(
doctest.DocTestSuite('utf8')
)
tests.addTests(
doctest.DocTestSuite('contrib.markmin.markmin2html',
)
)
return tests
if __name__ == '__main__':
unittest.main()
+140 -60
View File
@@ -248,6 +248,7 @@ class Mail(object):
settings.sender = sender
settings.login = login
settings.tls = tls
settings.hostname = None
settings.ssl = False
settings.cipher_type = None
settings.gpg_home = None
@@ -370,15 +371,16 @@ class Mail(object):
if not isinstance(sender, str):
raise Exception('Sender address not specified')
if not raw:
if not raw and attachments:
# Use multipart/mixed if there is attachments
payload_in = MIMEMultipart.MIMEMultipart('mixed')
else:
elif raw:
# no encoding configuration for raw messages
if not isinstance(message, basestring):
message = message.read()
if isinstance(message, unicode):
text = message.encode('utf-8')
elif not encoding=='utf-8':
elif not encoding == 'utf-8':
text = message.decode(encoding).encode('utf-8')
else:
text = message
@@ -410,21 +412,43 @@ class Mail(object):
html = None
if (not text is None or not html is None) and (not raw):
attachment = MIMEMultipart.MIMEMultipart('alternative')
if not text is None:
if isinstance(text, basestring):
if not isinstance(text, basestring):
text = text.read()
if isinstance(text, unicode):
text = text.encode('utf-8')
elif not encoding == 'utf-8':
text = text.decode(encoding).encode('utf-8')
else:
text = text.read().decode(encoding).encode('utf-8')
attachment.attach(MIMEText.MIMEText(text, _charset='utf-8'))
if not html is None:
if isinstance(html, basestring):
if not isinstance(html, basestring):
html = html.read()
if isinstance(html, unicode):
html = html.encode('utf-8')
elif not encoding == 'utf-8':
html = html.decode(encoding).encode('utf-8')
else:
html = html.read().decode(encoding).encode('utf-8')
# Construct mime part only if needed
if text and html:
# We have text and html we need multipart/alternative
attachment = MIMEMultipart.MIMEMultipart('alternative')
attachment.attach(MIMEText.MIMEText(text, _charset='utf-8'))
attachment.attach(
MIMEText.MIMEText(html, 'html', _charset='utf-8'))
payload_in.attach(attachment)
elif text:
attachment = MIMEText.MIMEText(text, _charset='utf-8')
elif html:
attachment = \
MIMEText.MIMEText(html, 'html', _charset='utf-8')
if attachments:
# If there is attachments put text and html into
# multipart/mixed
payload_in.attach(attachment)
else:
# No attachments no multipart/mixed
payload_in = attachment
if (attachments is None) or raw:
pass
elif isinstance(attachments, (list, tuple)):
@@ -682,9 +706,9 @@ class Mail(object):
else:
server = smtplib.SMTP(*smtp_args)
if self.settings.tls and not self.settings.ssl:
server.ehlo()
server.ehlo(self.settings.hostname)
server.starttls()
server.ehlo()
server.ehlo(self.settings.hostname)
if self.settings.login:
server.login(*self.settings.login.split(':', 1))
result = server.sendmail(
@@ -867,6 +891,7 @@ class Auth(object):
on_failed_authentication=lambda x: redirect(x),
formstyle="table3cols",
label_separator=": ",
allow_delete_accounts=False,
password_field='password',
table_user_name='auth_user',
table_group_name='auth_group',
@@ -892,6 +917,8 @@ class Auth(object):
username_case_sensitive=True,
update_fields = ['email'],
ondelete="CASCADE",
client_side = True,
wiki = Settings(),
)
# ## these are messages that can be customized
default_messages = dict(
@@ -899,7 +926,7 @@ class Auth(object):
register_button='Register',
password_reset_button='Request reset password',
password_change_button='Change password',
profile_save_button='Save profile',
profile_save_button='Apply changes',
submit_button='Submit',
verify_password='Verify Password',
delete_label='Check to delete',
@@ -1182,6 +1209,14 @@ class Auth(object):
# ## these are messages that can be customized
messages = self.messages = Messages(current.T)
messages.update(Auth.default_messages)
messages.update(ajax_failed_authentication=DIV(H4('NOT AUTHORIZED'),
'Please ',
A('login',
_href=self.settings.login_url +
('?_next=' + urllib.quote(current.request.env.http_web2py_component_location))
if current.request.env.http_web2py_component_location else ''),
' to view this content.',
_class='not-authorized alert alert-block'))
messages.lock_keys = True
# for "remember me" option
@@ -1738,7 +1773,7 @@ class Auth(object):
if 'registration_id' in checks \
and user \
and user.registration_id \
and user.registration_id != keys.get('registration_id', None):
and ('registration_id' not in keys or user.registration_id != str(keys['registration_id'])):
user = None # THINK MORE ABOUT THIS? DO WE TRUST OPENID PROVIDER?
if user:
update_keys = dict(registration_id=keys['registration_id'])
@@ -2120,7 +2155,7 @@ class Auth(object):
callback(onfail, None)
redirect(
self.url(args=request.args, vars=request.get_vars),
client_side=True)
client_side=self.settings.client_side)
else:
# use a central authentication server
@@ -2137,7 +2172,8 @@ class Auth(object):
else:
# we need to pass through login again before going on
next = self.url(self.settings.function, args='login')
redirect(cas.login_url(next), client_side=True)
redirect(cas.login_url(next),
client_side=self.settings.client_side)
# process authenticated users
if user:
@@ -2160,7 +2196,7 @@ class Auth(object):
if next == session._auth_next:
session._auth_next = None
next = replace_id(next, form)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
table_user[username].requires = old_requires
return form
@@ -2169,7 +2205,7 @@ class Auth(object):
if next == session._auth_next:
del session._auth_next
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT):
"""
@@ -2221,7 +2257,8 @@ class Auth(object):
response = current.response
session = current.session
if self.is_logged_in():
redirect(self.settings.logged_url, client_side=True)
redirect(self.settings.logged_url,
client_side=self.settings.client_side)
if next is DEFAULT:
next = self.next or self.settings.register_next
if onvalidation is DEFAULT:
@@ -2232,7 +2269,9 @@ class Auth(object):
log = self.messages.register_log
table_user = self.table_user()
if 'username' in table_user.fields:
if self.settings.login_userfield:
username = self.settings.login_userfield
elif 'username' in table_user.fields:
username = 'username'
else:
username = 'email'
@@ -2337,7 +2376,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
return form
def is_logged_in(self):
@@ -2585,7 +2624,7 @@ class Auth(object):
raise Exception
except Exception:
session.flash = self.messages.invalid_reset_password
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
passfield = self.settings.password_field
form = SQLFORM.factory(
Field('new_password', 'password',
@@ -2610,7 +2649,7 @@ class Auth(object):
session.flash = self.messages.password_changed
if self.settings.login_after_password_change:
self.login_user(user)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
return form
def request_reset_password(
@@ -2668,10 +2707,12 @@ class Auth(object):
user = table_user(email=form.vars.email)
if not user:
session.flash = self.messages.invalid_email
redirect(self.url(args=request.args), client_side=True)
redirect(self.url(args=request.args),
client_side=self.settings.client_side)
elif user.registration_key in ('pending', 'disabled', 'blocked'):
session.flash = self.messages.registration_pending
redirect(self.url(args=request.args), client_side=True)
redirect(self.url(args=request.args),
client_side=self.settings.client_side)
if self.email_reset_password(user):
session.flash = self.messages.email_sent
else:
@@ -2682,7 +2723,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
# old_requires = table_user.email.requires
return form
@@ -2727,7 +2768,8 @@ class Auth(object):
"""
if not self.is_logged_in():
redirect(self.settings.login_url, client_side=True)
redirect(self.settings.login_url,
client_side=self.settings.client_side)
db = self.db
table_user = self.table_user()
s = db(table_user.id == self.user.id)
@@ -2777,7 +2819,7 @@ class Auth(object):
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
return form
def profile(
@@ -2797,7 +2839,8 @@ class Auth(object):
table_user = self.table_user()
if not self.is_logged_in():
redirect(self.settings.login_url, client_side=True)
redirect(self.settings.login_url,
client_side=self.settings.client_side)
passfield = self.settings.password_field
table_user[passfield].writable = False
request = current.request
@@ -2820,20 +2863,24 @@ class Auth(object):
delete_label=self.messages.delete_label,
upload=self.settings.download_url,
formstyle=self.settings.formstyle,
separator=self.settings.label_separator
separator=self.settings.label_separator,
deletable=self.settings.allow_delete_accounts,
)
if form.accepts(request, session,
formname='profile',
onvalidation=onvalidation, hideerror=self.settings.hideerror):
onvalidation=onvalidation,
hideerror=self.settings.hideerror):
self.user.update(table_user._filter_fields(form.vars))
session.flash = self.messages.profile_updated
self.log_event(log, self.user)
callback(onaccept, form)
if form.deleted:
return self.logout()
if not next:
next = self.url(args=request.args)
else:
next = replace_id(next, form)
redirect(next, client_side=True)
redirect(next, client_side=self.settings.client_side)
return form
def is_impersonating(self):
@@ -2947,7 +2994,7 @@ class Auth(object):
if requires_login:
if not user:
if current.request.ajax:
raise HTTP(401)
raise HTTP(401, self.messages.ajax_failed_authentication)
elif not otherwise is None:
if callable(otherwise):
return otherwise()
@@ -2955,8 +3002,6 @@ class Auth(object):
elif self.settings.allow_basic_login_only or \
basic_accepted or current.request.is_restful:
raise HTTP(403, "Not authorized")
elif current.request.ajax:
return A('login', _href=self.settings.login_url)
else:
next = self.here()
current.session.flash = current.response.flash
@@ -3368,7 +3413,12 @@ class Auth(object):
resolve=True,
extra=None,
menu_groups=None,
templates=None):
templates=None,
migrate=True,
controller=None,
function=None):
if controller and function: resolve = False
if not hasattr(self, '_wiki'):
self._wiki = Wiki(self, render=render,
@@ -3377,9 +3427,13 @@ class Auth(object):
restrict_search=restrict_search,
env=env, extra=extra or {},
menu_groups=menu_groups,
templates=templates)
templates=templates,
migrate=migrate,
controller=controller,
function=function)
else:
self._wiki.env.update(env or {})
# if resolve is set to True, process request as wiki call
# resolve=False allows initial setup without wiki redirection
wiki = None
@@ -3396,6 +3450,13 @@ class Auth(object):
wiki = XML(wiki)
return wiki
def wikimenu(self):
"""to be used in menu.py for app wide wiki menus"""
if (hasattr(self, "_wiki") and
self._wiki.settings.controller and
self._wiki.settings.function):
self._wiki.automenu()
class Crud(object):
@@ -3448,7 +3509,7 @@ class Crud(object):
messages = self.messages = Messages(current.T)
messages.submit_button = 'Submit'
messages.delete_label = 'Check to delete:'
messages.delete_label = 'Check to delete'
messages.record_created = 'Record Created'
messages.record_updated = 'Record Updated'
messages.record_deleted = 'Record Deleted'
@@ -4975,9 +5036,10 @@ class Wiki(object):
def __init__(self, auth, env=None, render='markmin',
manage_permissions=False, force_prefix='',
restrict_search=False, extra=None,
menu_groups=None, templates=None):
menu_groups=None, templates=None, migrate=True,
controller=None, function=None):
settings = self.settings = Settings()
settings = self.settings = auth.settings.wiki
# render: "markmin", "html", ..., <function>
settings.render = render
@@ -4988,11 +5050,14 @@ class Wiki(object):
settings.extra = extra or {}
settings.menu_groups = menu_groups
settings.templates = templates
settings.controller = controller
settings.function = function
db = auth.db
self.env = env or {}
self.env['component'] = Wiki.component
self.auth = auth
self.wiki_menu_items = None
if self.auth.user:
self.settings.force_prefix = force_prefix % self.auth.user
@@ -5023,20 +5088,20 @@ class Wiki(object):
compute=self.get_render(),
readable=False, writable=False),
auth.signature],
'vars':{'format':'%(title)s'}}),
'vars':{'format':'%(title)s', 'migrate':migrate}}),
('wiki_tag', {
'args':[
Field('name'),
Field('wiki_page', 'reference wiki_page'),
auth.signature],
'vars':{'format':'%(name)s'}}),
'vars':{'format':'%(title)s', 'migrate':migrate}}),
('wiki_media', {
'args':[
Field('wiki_page', 'reference wiki_page'),
Field('title', required=True),
Field('filename', 'upload', required=True),
auth.signature],
'vars':{'format':'%(title)s'}})
'vars':{'format':'%(title)s', 'migrate':migrate}}),
]
# define only non-existent tables
@@ -5131,10 +5196,21 @@ class Wiki(object):
### END POLICY
def automenu(self):
"""adds the menu if not present"""
request = current.request
if not self.wiki_menu_items and self.settings.controller and self.settings.function:
self.wiki_menu_items = self.menu(self.settings.controller,
self.settings.function)
current.response.menu += self.wiki_menu_items
def __call__(self):
request = current.request
automenu = self.menu(request.controller, request.function)
current.response.menu += automenu
settings = self.settings
settings.controller = settings.controller or request.controller
settings.function = settings.function or request.function
self.automenu()
zero = request.args(0) or 'index'
if zero and zero.isdigit():
return self.media(int(zero))
@@ -5193,6 +5269,7 @@ class Wiki(object):
else:
return dict(title=page.title,
slug=page.slug,
page=page,
content=XML(self.fix_hostname(page.html)),
tags=page.tags,
created_on=page.created_on,
@@ -5205,6 +5282,7 @@ class Wiki(object):
else:
return dict(title=page.title,
slug=page.slug,
page=page,
content=page.body,
tags=page.tags,
created_on=page.created_on,
@@ -5255,25 +5333,27 @@ class Wiki(object):
current.session.flash = 'page created'
redirect(URL(args=slug))
script = """
$(function() {
jQuery(function() {
if (!jQuery('#wiki_page_body').length) return;
var pagecontent = jQuery('#wiki_page_body');
pagecontent.css('font-family',
'Monaco,Menlo,Consolas,"Courier New",monospace');
var prevbutton = jQuery('<button class="btn nopreview">Preview</button>');
var mediabutton = jQuery('<button class="btn nopreview">Media</button>');
var preview = jQuery('<div id="preview"></div>').hide();
var previewmedia = jQuery('<div id="previewmedia"></div>');
var form = pagecontent.closest('form');
preview.insertBefore(form);
prevbutton.insertBefore(form);
mediabutton.insertBefore(form);
previewmedia.insertBefore(form);
mediabutton.toggle(function() {
web2py_component('%(urlmedia)s', 'previewmedia');
}, function() {
previewmedia.empty();
});
if(%(link_media)s) {
var mediabutton = jQuery('<button class="btn nopreview">Media</button>');
mediabutton.insertBefore(form);
previewmedia.insertBefore(form);
mediabutton.toggle(function() {
web2py_component('%(urlmedia)s', 'previewmedia');
}, function() {
previewmedia.empty();
});
}
prevbutton.click(function(e) {
e.preventDefault();
if (prevbutton.hasClass('nopreview')) {
@@ -5288,7 +5368,7 @@ class Wiki(object):
}
})
})
""" % dict(url=URL(args=('_preview', slug)),
""" % dict(url=URL(args=('_preview', slug)),link_media=('true' if page else 'false'),
urlmedia=URL(extension='load',
args=('_editmedia',slug),
vars=dict(embedded=1)))
@@ -5348,7 +5428,7 @@ class Wiki(object):
'%(slug)s')),
comment=current.T(
"Choose Template or empty for new Page")))
form = SQLFORM.factory(*fields, **dict(_class="well span6"))
form = SQLFORM.factory(*fields, **dict(_class="well"))
form.element("[type=submit]").attributes["_value"] = \
current.T("Create Page from Slug")
@@ -5413,8 +5493,8 @@ class Wiki(object):
items = link[2:].split('/')
if len(items) > 3:
title_page = items[3]
link = URL(a=items[0] or None, c=items[1] or None,
f=items[2] or None, args=items[3:])
link = URL(a=items[0] or None, c=items[1] or controller,
f=items[2] or function, args=items[3:])
parent = tree.get(base[1:], tree[''])
subtree = []
tree[base] = subtree
+26 -9
View File
@@ -295,14 +295,31 @@ def is_valid_ip_address(address):
return True
def is_loopback_ip_address(ip):
"""Determines whether the IP address appears to be a loopback address.
This assumes that the IP is valid. The IPv6 check is limited to '::1'.
def is_loopback_ip_address(ip=None, addrinfo=None):
"""
if not ip:
Determines whether the address appears to be a loopback address.
This assumes that the IP is valid.
"""
if addrinfo: # see socket.getaddrinfo() for layout of addrinfo tuple
if addrinfo[0] == socket.AF_INET or addrinfo[0] == socket.AF_INET6:
ip = addrinfo[4]
if not isinstance(ip, basestring):
return False
if ip.count('.') == 3: # IPv4
return ip.startswith('127') or ip.startswith('::ffff:127')
return ip == '::1' # IPv6
# IPv4 or IPv6-embedded IPv4 or IPv4-compatible IPv6
if ip.count('.') == 3:
return ip.lower().startswith(('127', '::127', '0:0:0:0:0:0:127',
'::ffff:127', '0:0:0:0:0:ffff:127'))
return ip == '::1' or ip == '0:0:0:0:0:0:0:1' # IPv6 loopback
def getipaddrinfo(host):
"""
Filter out non-IP and bad IP addresses from getaddrinfo
"""
try:
return [addrinfo for addrinfo in socket.getaddrinfo(host, None)
if (addrinfo[0] == socket.AF_INET or
addrinfo[0] == socket.AF_INET6)
and isinstance(addrinfo[4][0], basestring)]
except socket.error:
return []
+228 -13
View File
@@ -51,6 +51,7 @@ __all__ = [
'IS_INT_IN_RANGE',
'IS_IPV4',
'IS_IPV6',
'IS_IPADDRESS',
'IS_LENGTH',
'IS_LIST_OF',
'IS_LOWER',
@@ -538,7 +539,7 @@ class IS_IN_DB(Validator):
records = self.dbset(table).select(table.ALL, **dd)
self.theset = [str(r[self.kfield]) for r in records]
if isinstance(self.label, str):
self.labels = [self.label % dict(r) for r in records]
self.labels = [self.label % r for r in records]
else:
self.labels = [self.label(r) for r in records]
@@ -648,11 +649,11 @@ class IS_NOT_IN_DB(Validator):
id = self.record_id
if isinstance(id, dict):
fields = [table[f] for f in id]
row = subset.select(*fields, **dict(limitby=(0, 1))).first()
row = subset.select(*fields, **dict(limitby=(0, 1), orderby_on_limitby=False)).first()
if row and any(str(row[f]) != str(id[f]) for f in id):
return (value, translate(self.error_message))
else:
row = subset.select(table._id, field, limitby=(0, 1)).first()
row = subset.select(table._id, field, limitby=(0, 1), orderby_on_limitby=False).first()
if row and str(row.id) != str(id):
return (value, translate(self.error_message))
return (value, None)
@@ -706,17 +707,20 @@ class IS_INT_IN_RANGE(Validator):
self.minimum = self.maximum = None
if minimum is None:
if maximum is None:
self.error_message = error_message or 'enter an integer'
self.error_message = translate(
error_message or 'enter an integer')
else:
self.maximum = int(maximum)
if error_message is None:
error_message = 'enter an integer less than or equal to %(max)g'
error_message = \
'enter an integer less than or equal to %(max)g'
self.error_message = translate(
error_message) % dict(max=self.maximum - 1)
elif maximum is None:
self.minimum = int(minimum)
if error_message is None:
error_message = 'enter an integer greater than or equal to %(min)g'
error_message = \
'enter an integer greater than or equal to %(min)g'
self.error_message = translate(
error_message) % dict(min=self.minimum)
else:
@@ -2510,17 +2514,18 @@ class IS_UPPER(Validator):
return (value.decode('utf8').upper().encode('utf8'), None)
def urlify(value, maxlen=80, keep_underscores=False):
def urlify(s, maxlen=80, keep_underscores=False):
"""
Convert incoming string to a simplified ASCII subset.
if (keep_underscores): underscores are retained in the string
else: underscores are translated to hyphens (default)
"""
s = value.lower() # to lowercase
s = s.decode('utf-8') # to utf-8
if isinstance(s, str):
s = s.decode('utf-8') # to unicode
s = s.lower() # to lowercase
s = unicodedata.normalize('NFKD', s) # normalize eg è => e, ñ => n
s = s.encode('ASCII', 'ignore') # encode as ASCII
s = re.sub('&\w+;', '', s) # strip html entities
s = s.encode('ascii', 'ignore') # encode as ASCII
s = re.sub('&\w+?;', '', s) # strip html entities
if keep_underscores:
s = re.sub('\s+', '-', s) # whitespace to hyphens
s = re.sub('[^\w\-]', '', s)
@@ -2654,8 +2659,8 @@ class IS_EMPTY_OR(Validator):
if hasattr(other, 'options'):
self.options = self._options
def _options(self):
options = self.other.options()
def _options(self, zero=False):
options = self.other.options(zero=zero)
if (not options or options[0][0] != '') and not self.multiple:
options.insert(0, ('', ''))
return options
@@ -3562,6 +3567,216 @@ class IS_IPV6(Validator):
return (value, translate(self.error_message))
class IS_IPADDRESS(Validator):
"""
Checks if field's value is an IP Address (v4 or v6). Can be set to force
addresses from within a specific range. Checks are done with the correct
IS_IPV4 and IS_IPV6 validators.
Uses ipaddress library if found, falls back to PEP-3144 ipaddr.py from
Google (in contrib).
Universal arguments:
minip: lowest allowed address; accepts:
str, eg. 192.168.0.1
list or tuple of octets, eg. [192, 168, 0, 1]
maxip: highest allowed address; same as above
invert: True to allow addresses only from outside of given range; note
that range boundaries are not matched this way
IPv4 specific arguments:
is_localhost: localhost address treatment:
None (default): indifferent
True (enforce): query address must match localhost address
(127.0.0.1)
False (forbid): query address must not match localhost
address
is_private: same as above, except that query address is checked against
two address ranges: 172.16.0.0 - 172.31.255.255 and
192.168.0.0 - 192.168.255.255
is_automatic: same as above, except that query address is checked against
one address range: 169.254.0.0 - 169.254.255.255
is_ipv4: None (default): indifferent
True (enforce): must be an IPv4 address
False (forbid): must NOT be an IPv4 address
IPv6 specific arguments:
is_link_local: Same as above but uses fe80::/10 range
is_reserved: Same as above but uses IETF reserved range
is_mulicast: Same as above but uses ff00::/8 range
is_routeable: Similar to above but enforces not private, link_local,
reserved or multicast
is_6to4: Same as above but uses 2002::/16 range
is_teredo: Same as above but uses 2001::/32 range
subnets: value must be a member of at least one from list of subnets
is_ipv6: None (default): indifferent
True (enforce): must be an IPv6 address
False (forbid): must NOT be an IPv6 address
Minip and maxip may also be lists or tuples of addresses in all above
forms (str, int, list / tuple), allowing setup of multiple address ranges:
minip = (minip1, minip2, ... minipN)
| | |
| | |
maxip = (maxip1, maxip2, ... maxipN)
Longer iterable will be truncated to match length of shorter one.
>>> IS_IPADDRESS()('192.168.1.5')
('192.168.1.5', None)
>>> IS_IPADDRESS(is_ipv6=False)('192.168.1.5')
('192.168.1.5', None)
>>> IS_IPADDRESS()('255.255.255.255')
('255.255.255.255', None)
>>> IS_IPADDRESS()('192.168.1.5 ')
('192.168.1.5 ', 'enter valid IP address')
>>> IS_IPADDRESS()('192.168.1.1.5')
('192.168.1.1.5', 'enter valid IP address')
>>> IS_IPADDRESS()('123.123')
('123.123', 'enter valid IP address')
>>> IS_IPADDRESS()('1111.2.3.4')
('1111.2.3.4', 'enter valid IP address')
>>> IS_IPADDRESS()('0111.2.3.4')
('0111.2.3.4', 'enter valid IP address')
>>> IS_IPADDRESS()('256.2.3.4')
('256.2.3.4', 'enter valid IP address')
>>> IS_IPADDRESS()('300.2.3.4')
('300.2.3.4', 'enter valid IP address')
>>> IS_IPADDRESS(minip='192.168.1.0', maxip='192.168.1.255')('192.168.1.100')
('192.168.1.100', None)
>>> IS_IPADDRESS(minip='1.2.3.5', maxip='1.2.3.9', error_message='bad ip')('1.2.3.4')
('1.2.3.4', 'bad ip')
>>> IS_IPADDRESS(maxip='1.2.3.4', invert=True)('127.0.0.1')
('127.0.0.1', None)
>>> IS_IPADDRESS(maxip='192.168.1.4', invert=True)('192.168.1.4')
('192.168.1.4', 'enter valid IP address')
>>> IS_IPADDRESS(is_localhost=True)('127.0.0.1')
('127.0.0.1', None)
>>> IS_IPADDRESS(is_localhost=True)('192.168.1.10')
('192.168.1.10', 'enter valid IP address')
>>> IS_IPADDRESS(is_localhost=False)('127.0.0.1')
('127.0.0.1', 'enter valid IP address')
>>> IS_IPADDRESS(maxip='100.0.0.0', is_localhost=True)('127.0.0.1')
('127.0.0.1', 'enter valid IP address')
>>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af')
('fe80::126c:8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(is_ipv4=False)('fe80::126c:8ffa:fe22:b3af')
('fe80::126c:8ffa:fe22:b3af', None)
>>> IS_IPADDRESS()('fe80::126c:8ffa:fe22:b3af ')
('fe80::126c:8ffa:fe22:b3af ', 'enter valid IP address')
>>> IS_IPADDRESS(is_ipv4=True)('fe80::126c:8ffa:fe22:b3af')
('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(is_ipv6=True)('192.168.1.1')
('192.168.1.1', 'enter valid IP address')
>>> IS_IPADDRESS(is_ipv6=True, error_message='bad ip')('192.168.1.1')
('192.168.1.1', 'bad ip')
>>> IS_IPADDRESS(is_link_local=True)('fe80::126c:8ffa:fe22:b3af')
('fe80::126c:8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(is_link_local=False)('fe80::126c:8ffa:fe22:b3af')
('fe80::126c:8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(is_link_local=True)('2001::126c:8ffa:fe22:b3af')
('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(is_multicast=True)('2001::126c:8ffa:fe22:b3af')
('2001::126c:8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(is_multicast=True)('ff00::126c:8ffa:fe22:b3af')
('ff00::126c:8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(is_routeable=True)('2001::126c:8ffa:fe22:b3af')
('2001::126c:8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(is_routeable=True)('ff00::126c:8ffa:fe22:b3af')
('ff00::126c:8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(subnets='2001::/32')('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(subnets='fb00::/8')('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', 'enter valid IP address')
>>> IS_IPADDRESS(subnets=['fc00::/8','2001::/32'])('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', None)
>>> IS_IPADDRESS(subnets='invalidsubnet')('2001::8ffa:fe22:b3af')
('2001::8ffa:fe22:b3af', 'invalid subnet provided')
"""
def __init__(
self,
minip='0.0.0.0',
maxip='255.255.255.255',
invert=False,
is_localhost=None,
is_private=None,
is_automatic=None,
is_ipv4=None,
is_link_local=None,
is_reserved=None,
is_multicast=None,
is_routeable=None,
is_6to4=None,
is_teredo=None,
subnets=None,
is_ipv6=None,
error_message='enter valid IP address'):
self.minip = minip,
self.maxip = maxip,
self.invert = invert
self.is_localhost = is_localhost
self.is_private = is_private
self.is_automatic = is_automatic
self.is_ipv4 = is_ipv4
self.is_private = is_private
self.is_link_local = is_link_local
self.is_reserved = is_reserved
self.is_multicast = is_multicast
self.is_routeable = is_routeable
self.is_6to4 = is_6to4
self.is_teredo = is_teredo
self.subnets = subnets
self.is_ipv6 = is_ipv6
self.error_message = error_message
def __call__(self, value):
try:
import ipaddress
except ImportError:
from contrib import ipaddr as ipaddress
try:
ip = ipaddress.ip_address(value)
except ValueError, e:
return (value, translate(self.error_message))
if self.is_ipv4 and isinstance(ip, ipaddress.IPv6Address):
retval = (value, translate(self.error_message))
elif self.is_ipv6 and isinstance(ip, ipaddress.IPv4Address):
retval = (value, translate(self.error_message))
elif self.is_ipv4 or isinstance(ip, ipaddress.IPv4Address):
retval = IS_IPV4(
minip=self.minip,
maxip=self.maxip,
invert=self.invert,
is_localhost=self.is_localhost,
is_private=self.is_private,
is_automatic=self.is_automatic,
error_message=self.error_message
)(value)
elif self.is_ipv6 or isinstance(ip, ipaddress.IPv6Address):
retval = IS_IPV6(
is_private=self.is_private,
is_link_local=self.is_link_local,
is_reserved=self.is_reserved,
is_multicast=self.is_multicast,
is_routeable=self.is_routeable,
is_6to4=self.is_6to4,
is_teredo=self.is_teredo,
subnets=self.subnets,
error_message=self.error_message
)(value)
else:
retval = (value, translate(self.error_message))
return retval
if __name__ == '__main__':
import doctest
doctest.testmod(
+40 -17
View File
@@ -15,7 +15,6 @@ import cStringIO
import time
import thread
import threading
import re
import os
import socket
import signal
@@ -25,10 +24,10 @@ import newcron
import getpass
import main
from fileutils import w2p_pack, read_file, write_file, create_welcome_w2p
from fileutils import read_file, write_file, create_welcome_w2p
from settings import global_settings
from shell import run, test
from utils import is_valid_ip_address, is_loopback_ip_address
from utils import is_valid_ip_address, is_loopback_ip_address, getipaddrinfo
try:
import Tkinter
@@ -62,22 +61,36 @@ if not sys.version[:3] in ['2.4', '2.5', '2.6', '2.7']:
logger = logging.getLogger("web2py")
def run_system_tests():
def run_system_tests(options):
"""
Runs unittests for gluon.tests
"""
import subprocess
major_version = sys.version_info[0]
minor_version = sys.version_info[1]
print "minor_version = %r" % minor_version
if major_version == 2:
if minor_version in (5, 6):
print "Python 2.5 or 2.6"
ret = os.system("PYTHONPATH=. unit2 -v gluon.tests")
sys.stderr.write("Python 2.5 or 2.6\n")
ret = subprocess.call(['unit2', '-v', 'gluon.tests'])
elif minor_version in (7,):
print "Python 2.7"
ret = os.system("PYTHONPATH=. python -m unittest -v gluon.tests")
call_args = [sys.executable, '-m', 'unittest', '-v', 'gluon.tests']
if options.with_coverage:
try:
import coverage
coverage_config = os.environ.get("COVERAGE_PROCESS_START",
os.path.join('gluon', 'tests', 'coverage.ini')
)
call_args = ['coverage', 'run', '--rcfile=%s' % coverage_config,
'-m', 'unittest', '-v', 'gluon.tests']
except:
sys.stderr.write('Coverage was not installed, skipping\n')
sys.stderr.write("Python 2.7\n")
ret = subprocess.call(call_args)
else:
print "unknown python 2.x version"
sys.stderr.write("unknown python 2.x version\n")
ret = 256
else:
print "Only Python 2.x supported."
sys.stderr.write("Only Python 2.x supported.\n")
ret = 256
sys.exit(ret and 1)
@@ -324,7 +337,6 @@ class web2pyDialog(object):
self.tb = None
def update_schedulers(self, start=False):
x = 0
apps = []
available_apps = [arq for arq in os.listdir('applications/')]
available_apps = [arq for arq in available_apps
@@ -902,7 +914,18 @@ def console():
dest='run_system_tests',
default=False,
help=msg)
msg = ('adds coverage reporting (needs --run_system_tests), '
'python 2.7 and the coverage module installed. '
'You can alter the default path setting the environmental '
'var "COVERAGE_PROCESS_START". '
'By default it takes gluon/tests/coverage.ini')
parser.add_option('--with_coverage',
action='store_true',
dest='with_coverage',
default=False,
help=msg)
if '-A' in sys.argv:
k = sys.argv.index('-A')
elif '--args' in sys.argv:
@@ -916,14 +939,14 @@ def console():
global_settings.cmd_args = args
try:
options.ips = list(set([
ip[4][0] for ip in socket.getaddrinfo(socket.getfqdn(), 0)
if not is_loopback_ip_address(ip[4][0])]))
options.ips = list(set( # no duplicates
[addrinfo[4][0] for addrinfo in getipaddrinfo(socket.getfqdn())
if not is_loopback_ip_address(addrinfo=addrinfo)]))
except socket.gaierror:
options.ips = []
if options.run_system_tests:
run_system_tests()
run_system_tests(options)
if options.quiet:
capture = cStringIO.StringIO()
+2
View File
@@ -136,6 +136,8 @@ class Web2pyService(Service):
path=options.folder
)
try:
from rewrite import load
load()
self.server.start()
except:
@@ -0,0 +1,206 @@
#!/bin/bash
# Autor: Nilton OS -- www.linuxpro.com.br
echo 'setup-web2py-nginx-uwsgi-centos64.sh'
echo 'Support CentOS 6.4'
echo 'Installs Nginx 1.4 + uWSGI + Web2py'
# Get Web2py Admin Password
echo -e "Web2py Admin Password: \c "
read PW
echo -e "Set Server Name Ex: web2py.domain.com : \c "
read SERVER_FQDN
echo -e "Set Server IP: \c "
read SERVER_IP
echo "" >>/etc/hosts
echo "$SERVER_IP $SERVER_FQDN" >>/etc/hosts
yum update -y
yum install -y http://mirror-fpt-telecom.fpt.net/fedora/epel/6/i386/epel-release-6-8.noarch.rpm
yum clean all
yum install -y gcc libxml2-devel python-devel python-pip PyXML unzip make sudo
## 64Bits System
## yum install -y http://nginx.org/packages/rhel/6/x86_64/RPMS/nginx-1.4.0-1.el6.ngx.x86_64.rpm
yum install -y http://nginx.org/packages/rhel/6/i386/RPMS/nginx-1.4.0-1.el6.ngx.i386.rpm
pip-python install --upgrade pip
PIPPATH=`which pip`
$PIPPATH install --upgrade uwsgi
# Prepare folders for uwsgi
mkdir /etc/uwsgi
mkdir /var/log/uwsgi
mkdir -p /var/www/
#usermod -a -G apache nginx
mkdir -p /etc/nginx/ssl/
cd /etc/nginx/ssl
openssl genrsa 1024 > web2py.key && chmod 400 web2py.key
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
echo 'server {
listen YOUR_SERVER_IP:80;
server_name YOUR_SERVER_FQDN;
#to enable correct use of response.static_version
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
# alias /var/www/web2py/applications/$1/static/$2;
# expires max;
#}
location ~* /(\w+)/static/ {
root /var/www/web2py/applications/;
#remove next comment on production
#expires max;
}
location / {
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///var/www/web2py/logs/web2py.socket;
include /etc/nginx/uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
}
server {
listen YOUR_SERVER_IP:443 default_server ssl;
server_name YOUR_SERVER_FQDN;
ssl_certificate /etc/nginx/ssl/web2py.crt;
ssl_certificate_key /etc/nginx/ssl/web2py.key;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
ssl_protocols SSLv3 TLSv1;
keepalive_timeout 70;
location / {
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///var/www/web2py/logs/web2py.socket;
include /etc/nginx/uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
}' >/etc/nginx/conf.d/web2py.conf
sed -i "s/YOUR_SERVER_IP/$SERVER_IP/" /etc/nginx/conf.d/web2py.conf
sed -i "s/YOUR_SERVER_FQDN/$SERVER_FQDN/" /etc/nginx/conf.d/web2py.conf
# Create configuration file /etc/uwsgi/web2py.xml
echo '<uwsgi>
<socket>/var/www/web2py/logs/web2py.socket</socket>
<pythonpath>/var/www/web2py/</pythonpath>
<mount>/=wsgihandler:application</mount>
<master/>
<processes>4</processes>
<harakiri>60</harakiri>
<reload-mercy>8</reload-mercy>
<cpu-affinity>1</cpu-affinity>
<stats>/var/www/web2py/logs/stats.socket</stats>
<max-requests>2000</max-requests>
<limit-as>512</limit-as>
<reload-on-as>256</reload-on-as>
<reload-on-rss>192</reload-on-rss>
<uid>nginx</uid>
<gid>nginx</gid>
<cron>0 0 -1 -1 -1 python /var/www/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron>
<no-orphans/>
</uwsgi>' >/etc/uwsgi/web2py.xml
cd /var/www/
curl --progress -O http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip && rm -rf web2py_src.zip
# Download latest version of sessions2trash.py
curl --output /var/www/web2py/scripts/sessions2trash.py http://web2py.googlecode.com/hg/scripts/sessions2trash.py
chown -R nginx:nginx web2py
cd /var/www/web2py
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
## Daemons /start/stop
echo '#!/bin/sh
# Autor: Nilton OS -- www.linuxpro.com.br
#
#
### BEGIN INIT INFO
# Provides: uwsgi
# Required-Start: $syslog $remote_fs
# Should-Start: $time ypbind smtp
# Required-Stop: $syslog $remote_fs
# Should-Stop: ypbind smtp
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
### END INIT INFO
# Source function library.
. /etc/rc.d/init.d/functions
# Check for missing binaries (stale symlinks should not happen)
UWSGI_BIN=`which uwsgi`
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
if [ "$1" = "stop" ]; then exit 0;
else exit 5; fi; }
UWSGI_EMPEROR_MODE=true
UWSGI_VASSALS="/etc/uwsgi/"
UWSGI_OPTIONS="--enable-threads --logto /var/log/uwsgi/uwsgi.log"
lockfile=/var/lock/subsys/uwsgi
UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
fi
case "$1" in
start)
echo -n "Starting uWSGI "
daemon $UWSGI_BIN $UWSGI_OPTIONS &
;;
stop)
echo -n "Shutting down uWSGI "
killproc $UWSGI_BIN
;;
restart)
$0 stop
$0 start
;;
status)
echo -n "Checking for service uWSGI "
status $UWSGI_BIN
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
;;
esac
exit 0 '> /etc/init.d/uwsgi
chmod +x /etc/init.d/uwsgi
/etc/init.d/uwsgi start
/etc/init.d/nginx start
/etc/init.d/iptables stop
chkconfig --del iptables
chkconfig --levels 235 uwsgi on
chkconfig --levels 235 nginx on
## you can reload uwsgi with
#/etc/init.d/uwsgi restart
## to reload web2py only (without restarting uwsgi)
# touch /etc/uwsgi/web2py.xml
@@ -0,0 +1,226 @@
#!/bin/bash
echo 'setup-web2py-nginx-uwsgi-opensuse.sh'
echo 'Requires OpenSUSE 12.3 32Bits and installs Nginx + uWSGI + Web2py'
# Get Web2py Admin Password
echo -e "Web2py Admin Password: \c "
read PW
zypper clean && zypper refresh && zypper up
zypper in -y nginx python-xml python-pip unzip sudo
zypper in -y gcc python-devel libxml2-devel python-pip unzip
pip install --upgrade pip
PIPPATH=`which pip`
$PIPPATH install --upgrade uwsgi
# Prepare folders for uwsgi
mkdir /etc/uwsgi
mkdir /var/log/uwsgi
usermod -G www nginx
mkdir -p /etc/nginx/vhosts.d/
mkdir -p /etc/nginx/ssl/
cd /etc/nginx/ssl
openssl genrsa 1024 > web2py.key
chmod 400 web2py.key
openssl req -new -x509 -nodes -sha1 -days 1780 -key web2py.key > web2py.crt
openssl x509 -noout -fingerprint -text < web2py.crt > web2py.info
echo 'server {
listen 80;
server_name $hostname;
#to enable correct use of response.static_version
#location ~* /(\w+)/static(?:/_[\d]+\.[\d]+\.[\d]+)?/(.*)$ {
# alias /srv/www/web2py/applications/$1/static/$2;
# expires max;
#}
location ~* /(\w+)/static/ {
root /srv/www/web2py/applications/;
#remove next comment on production
#expires max;
}
location / {
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///tmp/web2py.socket;
include /etc/nginx/uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
}
server {
listen 443 default_server ssl;
server_name $hostname;
ssl_certificate /etc/nginx/ssl/web2py.crt;
ssl_certificate_key /etc/nginx/ssl/web2py.key;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
ssl_protocols SSLv3 TLSv1;
keepalive_timeout 70;
location / {
#uwsgi_pass 127.0.0.1:9001;
uwsgi_pass unix:///tmp/web2py.socket;
include /etc/nginx/uwsgi_params;
uwsgi_param UWSGI_SCHEME $scheme;
uwsgi_param SERVER_SOFTWARE nginx/$nginx_version;
}
}' >/etc/nginx/vhosts.d/web2py.conf
# Create configuration file /etc/uwsgi/web2py.xml
echo '<uwsgi>
<socket>/tmp/web2py.socket</socket>
<pythonpath>/srv/www/web2py/</pythonpath>
<mount>/=wsgihandler:application</mount>
<master/>
<processes>4</processes>
<harakiri>60</harakiri>
<reload-mercy>8</reload-mercy>
<cpu-affinity>1</cpu-affinity>
<stats>/tmp/stats.socket</stats>
<max-requests>2000</max-requests>
<limit-as>512</limit-as>
<reload-on-as>256</reload-on-as>
<reload-on-rss>192</reload-on-rss>
<uid>nginx</uid>
<gid>www</gid>
<cron>0 0 -1 -1 -1 python /srv/www/web2py/web2py.py -Q -S welcome -M -R scripts/sessions2trash.py -A -o</cron>
<no-orphans/>
</uwsgi>' >/etc/uwsgi/web2py.xml
cd /srv/www/
wget http://web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip
rm web2py_src.zip
# Download latest version of sessions2trash.py
wget http://web2py.googlecode.com/hg/scripts/sessions2trash.py -O /srv/www/web2py/scripts/sessions2trash.py
chown -R nginx:www web2py
cd /srv/www/web2py
sudo -u nginx python -c "from gluon.main import save_password; save_password('$PW',443)"
## Daemons /start/stop
echo '#!/bin/sh
# Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
#
# Author: James Oakley
#
# /etc/init.d/uwsgi
# and its symbolic link
# /(usr/)sbin/rcuwsgi
#
# LSB compatible service control script; see http://www.linuxbase.org/spec/
#
### BEGIN INIT INFO
# Provides: uwsgi
# Required-Start: $syslog $remote_fs
# Should-Start: $time ypbind smtp
# Required-Stop: $syslog $remote_fs
# Should-Stop: ypbind smtp
# Default-Start: 3 5
# Default-Stop: 0 1 2 6
# Short-Description: Application Container Server for Networked/Clustered Web Applications
# Description: Application Container Server for Networked/Clustered Web Applications
### END INIT INFO
# Check for missing binaries (stale symlinks should not happen)
UWSGI_BIN=/usr/bin/uwsgi
test -x $UWSGI_BIN || { echo "$UWSGI_BIN not installed";
if [ "$1" = "stop" ]; then exit 0;
else exit 5; fi; }
UWSGI_EMPEROR_MODE=true
UWSGI_VASSALS="/etc/uwsgi/"
UWSGI_OPTIONS="--logto /var/log/uwsgi/uwsgi.log"
UWSGI_OPTIONS="$UWSGI_OPTIONS --autoload"
if [ "$UWSGI_EMPEROR_MODE" = "true" ] ; then
UWSGI_OPTIONS="$UWSGI_OPTIONS --emperor $UWSGI_VASSALS"
fi
. /etc/rc.status
rc_reset
case "$1" in
start)
echo -n "Starting uWSGI "
/sbin/startproc $UWSGI_BIN $UWSGI_OPTIONS
rc_status -v
;;
stop)
echo -n "Shutting down uWSGI "
/sbin/killproc $UWSGI_BIN
rc_status -v
;;
try-restart|condrestart)
if test "$1" = "condrestart"; then
echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}"
fi
$0 status
if test $? = 0; then
$0 restart
else
rc_reset
fi
rc_status
;;
restart)
$0 stop
$0 start
rc_status
;;
force-reload)
echo -n "Reload service uWSGI "
/sbin/killproc -HUP $UWSGI_BIN
rc_status -v
;;
reload)
echo -n "Reload service uWSGI "
/sbin/killproc -HUP $UWSGI_BIN
rc_status -v
;;
status)
echo -n "Checking for service uWSGI "
/sbin/checkproc $UWSGI_BIN
rc_status -v
;;
probe)
echo -n "uWSGI does not support probe "
rc_failed 3
rc_status -v
;;
*)
echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}"
exit 1
;;
esac
rc_exit '> /etc/init.d/uwsgi
chmod +x /etc/init.d/uwsgi
/etc/init.d/uwsgi start
/etc/init.d/nginx restart
chkconfig --add uwsgi
chkconfig --add nginx
## you can reload uwsgi with
#/etc/init.d/uwsgi restart
## to reload web2py only (without restarting uwsgi)
# touch /etc/uwsgi/web2py.xml
+6
View File
@@ -24,4 +24,10 @@ if __name__ == '__main__':
freeze_support()
except:
sys.stderr.write('Sorry, -K only supported for python 2.6-2.7\n')
if os.environ.has_key("COVERAGE_PROCESS_START"):
try:
import coverage
coverage.process_startup()
except:
pass
gluon.widget.start(cron=True)